<!DOCTYPE html>
<html >
<head>

    <!--[if lt IE 9]>
        <style>body {display: none; background: none !important} </style>
        <meta http-equiv="Refresh" Content="0; url=//outdatedbrowser.com/" />
    <![endif]-->

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="format-detection" content="telephone=no" />
<meta name="author" content="Shang Jianli" />


    
    


<meta property="og:type" content="website">
<meta property="og:title" content="畅游大数据">
<meta property="og:url" content="http://yoursite.com/index.html">
<meta property="og:site_name" content="畅游大数据">
<meta property="og:locale" content="default">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="畅游大数据">

<link rel="apple-touch-icon" href= "/apple-touch-icon.png">


    <link rel="alternate" href="/atom.xml" title="畅游大数据" type="application/atom+xml">



    <link rel="shortcut icon" href="/favicon.png">



    <link href="//cdn.bootcss.com/animate.css/3.5.1/animate.min.css" rel="stylesheet">



    <link href="//cdn.bootcss.com/fancybox/2.1.5/jquery.fancybox.min.css" rel="stylesheet">



    <script src="//cdn.bootcss.com/pace/1.0.2/pace.min.js"></script>
    <link href="//cdn.bootcss.com/pace/1.0.2/themes/blue/pace-theme-minimal.css" rel="stylesheet">


<link rel="stylesheet" href="/css/style.css">


    <style> .article { opacity: 0;} </style>


<link href="//cdn.bootcss.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet">


<title>畅游大数据</title>

<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
<script src="//cdn.bootcss.com/clipboard.js/1.5.10/clipboard.min.js"></script>

<script>
    var yiliaConfig = {
        fancybox: true,
        animate: true,
        isHome: true,
        isPost: false,
        isArchive: false,
        isTag: false,
        isCategory: false,
        fancybox_js: "//cdn.bootcss.com/fancybox/2.1.5/jquery.fancybox.min.js",
        scrollreveal: "//cdn.bootcss.com/scrollReveal.js/3.1.4/scrollreveal.min.js",
        search: 
    }
</script>


    <script> yiliaConfig.jquery_ui = [false]; </script>



    <script> yiliaConfig.rootUrl = "\/";</script>






</head>
<body>
  <div id="container">
    <div class="left-col">
    <div class="overlay"></div>
<div class="intrude-less">
    <header id="header" class="inner">
        <a href="/" class="profilepic">
            <img src="/img/IMGP9014.JPG" class="animated zoomIn">
        </a>
        <hgroup>
          <h1 class="header-author"><a href="/">Shang Jianli</a></h1>
        </hgroup>

        

        


        
            <div id="switch-btn" class="switch-btn">
                <div class="icon">
                    <div class="icon-ctn">
                        <div class="icon-wrap icon-house" data-idx="0">
                            <div class="birdhouse"></div>
                            <div class="birdhouse_holes"></div>
                        </div>
                        <div class="icon-wrap icon-ribbon hide" data-idx="1">
                            <div class="ribbon"></div>
                        </div>
                        
                        <div class="icon-wrap icon-link hide" data-idx="2">
                            <div class="loopback_l"></div>
                            <div class="loopback_r"></div>
                        </div>
                        
                        
                        <div class="icon-wrap icon-me hide" data-idx="3">
                            <div class="user"></div>
                            <div class="shoulder"></div>
                        </div>
                        
                    </div>
                    
                </div>
                <div class="tips-box hide">
                    <div class="tips-arrow"></div>
                    <ul class="tips-inner">
                        <li>菜单</li>
                        <li>标签</li>
                        
                        <li>友情链接</li>
                        
                        
                        <li>关于我</li>
                        
                    </ul>
                </div>
            </div>
        

        <div id="switch-area" class="switch-area">
            <div class="switch-wrap">
                <section class="switch-part switch-part1">
                    <nav class="header-menu">
                        <ul>
                        
                            <li><a href="/archives/">主页</a></li>
                        
                            <li><a href="/archives/">所有文章</a></li>
                        
                            <li><a href="/tags/">标签云</a></li>
                        
                            <li><a href="/about/">关于我</a></li>
                        
                        </ul>
                    </nav>
                    <nav class="header-nav">
                        <ul class="social">
                            
                                <a class="fa Email" href="mailto:123@123.com" title="Email"></a>
                            
                                <a class="fa GitHub" href="#" title="GitHub"></a>
                            
                                <a class="fa RSS" href="/atom.xml" title="RSS"></a>
                            
                        </ul>
                    </nav>
                </section>
                
                
                <section class="switch-part switch-part2">
                    <div class="widget tagcloud" id="js-tagcloud">
                        <ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/Docker/">Docker</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Kafka/">Kafka</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Redis/">Redis</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/hive/">hive</a></li></ul>
                    </div>
                </section>
                
                
                
                <section class="switch-part switch-part3">
                    <div id="js-friends">
                    
                      <a class="main-nav-link switch-friends-link" href="https://hexo.io">Hexo</a>
                    
                      <a class="main-nav-link switch-friends-link" href="https://pages.github.com/">GitHub</a>
                    
                      <a class="main-nav-link switch-friends-link" href="http://moxfive.xyz/">MOxFIVE</a>
                    
                    </div>
                </section>
                

                
                
                <section class="switch-part switch-part4">
                
                    <div id="js-aboutme">专注于前端</div>
                </section>
                
            </div>
        </div>
    </header>                
</div>
    </div>
    <div class="mid-col">
      <nav id="mobile-nav">
      <div class="overlay">
          <div class="slider-trigger"></div>
          <h1 class="header-author js-mobile-header hide"><a href="/" title="回到主页">Shang Jianli</a></h1>
      </div>
    <div class="intrude-less">
        <header id="header" class="inner">
            <a href="/" class="profilepic">
                <img src="/img/IMGP9014.JPG" class="animated zoomIn">
            </a>
            <hgroup>
              <h1 class="header-author"><a href="/" title="回到主页">Shang Jianli</a></h1>
            </hgroup>
            
            <nav class="header-menu">
                <ul>
                
                    <li><a href="/archives/">主页</a></li>
                
                    <li><a href="/archives/">所有文章</a></li>
                
                    <li><a href="/tags/">标签云</a></li>
                
                    <li><a href="/about/">关于我</a></li>
                
                <div class="clearfix"></div>
                </ul>
            </nav>
            <nav class="header-nav">
                        <ul class="social">
                            
                                <a class="fa Email" target="_blank" href="mailto:123@123.com" title="Email"></a>
                            
                                <a class="fa GitHub" target="_blank" href="#" title="GitHub"></a>
                            
                                <a class="fa RSS" target="_blank" href="/atom.xml" title="RSS"></a>
                            
                        </ul>
            </nav>
        </header>                
    </div>
    <link class="menu-list" tags="标签" friends="友情链接" about="关于我"/>
</nav>
      <div class="body-wrap">
  
    <article id="post-Win10下安装Docker并将windows文件夹挂载到Docker和linux上" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2018/11/15/Win10下安装Docker并将windows文件夹挂载到Docker和linux上/" class="article-date">
      <time datetime="2018-11-15T10:23:08.000Z" itemprop="datePublished">2018-11-15</time>
</a>


    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2018/11/15/Win10下安装Docker并将windows文件夹挂载到Docker和linux上/">Win10下安装Docker并将windows文件夹挂载到Docker和linux上</a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
          
        <p>&ensp;&ensp;&ensp;&ensp;Docker 是一个开源的应用容器引擎，基于 Go 语言 并遵从Apache2.0协议开源。<br>Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中，然后发布到任何流行的 Linux 机器上，也可以实现虚拟化。<br>容器是完全使用沙箱机制，相互之间不会有任何接口（类似 iPhone 的 app）,更重要的是容器性能开销极低。<br><strong>下面是我从零了解Docker的过程：</strong></p>
<h2 id="下载安装："><a href="#下载安装：" class="headerlink" title="下载安装："></a>下载安装：</h2><h4 id="Windows下载Docker-toolbox网址"><a href="#Windows下载Docker-toolbox网址" class="headerlink" title="Windows下载Docker-toolbox网址"></a>Windows下载Docker-toolbox网址</h4><blockquote>
<p><a href="http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/" target="_blank" rel="noopener">http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/</a></p>
</blockquote>
<h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><blockquote>
<p>运行 DockerToolbox-18.03.0-ce.exe 进行安装，除了更改安装位置外，其他默认，Docker会自动安装VirtualBox和Kitematic。<br>默认情况下启动Docker，Docker会将镜像文件存储在C盘，因此需要设置镜像文件存储路径：开始-&gt;git bash-&gt;输入：notepad .bash_profile 创建并打开.bash_profile 配置文件-&gt;空白处写：<br>export MACHINE_STORAGE_PATH=’E:\soft\docker’<br>-&gt;保存退出 -&gt;在E:\soft\docker下创建cache文件夹，将安装目录下的boot2docker.iso拷贝到该文件夹</p>
</blockquote>
<h4 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h4><blockquote>
<p>在桌面上单机DockerQuickStart，创建虚拟机并将镜像源改为国内服务器，这边改成了阿里云，具体操作是执行如下命令：<br>docker-machine -s “E:\soft\docker” create –engine-registry-mirror=<a href="https://vf29u5xi.mirror.aliyuncs.com" target="_blank" rel="noopener">https://vf29u5xi.mirror.aliyuncs.com</a> -d virtualbox default<br>最后的default是Docker容器的名称，可以修改。</p>
</blockquote>
<p><strong>启动后，你会看到该docker的ip，可以以使用xshell等工具进行连接，用户名是docker,密码是tcuser</strong></p>
<h4 id="操作"><a href="#操作" class="headerlink" title="操作"></a>操作</h4><p><strong>1、下载Linux镜像</strong></p>
<blockquote>
<p><strong>执行命令： docker pull centos 下载Linux镜像</strong><br><strong>完成后 执行 docker  images  查看镜像的信息</strong><br><strong>安装镜像   docker run -d -i -t imageID /bin/bash</strong><br><strong>安装完成后 执行 docker ps  查看 安装的镜像，如有 镜像的信息 即代表 安装成功</strong><br><strong>进入 Linux 执行 docker attach  CONTAINER ID 即可</strong></p>
</blockquote>
<p><strong>2、玩转linux</strong></p>
<blockquote>
<p>yum下载vim、gcc、wget等<br><strong>然后你会发现一个非常蛋疼的事情：</strong><br><strong>你没办法讲文件从windows环境传到linux系统文件中！！！</strong><br>解决办法：接着查资料吧<br><strong>解决步骤：</strong><br><a href="https://blog.csdn.net/u014149394/article/details/79162283" target="_blank" rel="noopener">https://blog.csdn.net/u014149394/article/details/79162283</a><br>1、进入virtualBox-&gt;设置-&gt;共享文件夹配置windows环境下共享的文件夹，取消自动挂载<br>2、将这个文件先挂载到docker<br>进入dockerquickstart，然后执行 docker-machine ssh default进入到default容器<br>切换到root用户（sudo -i），创建一个文件夹用于挂载位置<br>执行：sudo mount -t vboxsf docker /mnt/docker   (docker就是配置virtralbox共享文件夹时的文件名，/mnt/docker是docker的被挂载位置，提前要创建)<br>exit退出<br>执行：docker run –name centos_vim_gcc_jdk_wget -it  -d -v /mnt/docker_share:/share centos_vim_gcc_jdk_wget<br>3、over</p>
</blockquote>

      
    </div>
    
    <div class="article-info article-info-index">
      
      

      
    <div class="article-tag tagcloud">
        <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Docker/">Docker</a></li></ul>
    </div>

      
      <div class="clearfix"></div>
    </div>
    
  </div>
  
</article>









  
    <article id="post-hive执行sql报错——incorrect-data-check-原因及解决办法" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2018/10/16/hive执行sql报错——incorrect-data-check-原因及解决办法/" class="article-date">
      <time datetime="2018-10-16T10:46:23.000Z" itemprop="datePublished">2018-10-16</time>
</a>


    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2018/10/16/hive执行sql报错——incorrect-data-check-原因及解决办法/">hive执行sql报错:incorrect data check 原因及解决办法</a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
          
        <p>&ensp;&ensp;&ensp;&ensp;执行hiveSQL过程中发现一个非常无解的问题，就是将 .gz 格式的日志文件映射成hive表，使用hive进行日志清洗过程中报错：Failed with exception java.io.IOException:java.io.IOException: incorrect data check。网上各种查资料，没有发现相关文档。现将问题原因和解决办法总结如下</p>
<h2 id="报错原因："><a href="#报错原因：" class="headerlink" title="报错原因："></a>报错原因：</h2><p>&ensp;&ensp;&ensp;&ensp;通过对映射的表做count()等操作，发现执行过程中报同样错误，讨论认为是该 .gz 格式的压缩文件损坏导致hive扫描数据出错，将该文件从hdfs上下载到本机进行解压，失败！！！！<br>&ensp;&ensp;&ensp;&ensp;太完美了，不是清洗过程的问题。是源文件损坏导致数据检查错误。做程序员，发现问题所在简直太令人兴奋了！！</p>
<h2 id="解决办法："><a href="#解决办法：" class="headerlink" title="解决办法："></a>解决办法：</h2><p>&ensp;&ensp;&ensp;&ensp;easy的不能再easy了，找运维小哥哥拿到源文件，gzip压缩，上传到hdfs相应目录，重跑任务，OK！问题解决！！</p>

      
    </div>
    
    <div class="article-info article-info-index">
      
      

      
    <div class="article-tag tagcloud">
        <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/hive/">hive</a></li></ul>
    </div>

      
      <div class="clearfix"></div>
    </div>
    
  </div>
  
</article>









  
    <article id="post-分布式流处理新贵Kafka-Stream" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2018/09/18/分布式流处理新贵Kafka-Stream/" class="article-date">
      <time datetime="2018-09-18T02:48:04.000Z" itemprop="datePublished">2018-09-18</time>
</a>


    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2018/09/18/分布式流处理新贵Kafka-Stream/">分布式流处理新贵Kafka Stream</a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
          
        <p>&ensp;&ensp;&ensp;&ensp;本文介绍了Kafka Stream的背景，如Kafka Stream是什么，什么是流式计算，以及为什么要有Kafka Stream。接着介绍了Kafka Stream的整体架构，并行模型，状态存储，以及主要的两种数据集KStream和KTable。并且分析了Kafka Stream如何解决流式系统中的关键问题，如时间定义，窗口操作，Join操作，聚合操作，以及如何处理乱序和提供容错能力。最后结合示例讲解了如何使用Kafka Stream。</p>
<h2 id="Kafka-Stream背景"><a href="#Kafka-Stream背景" class="headerlink" title="Kafka Stream背景"></a>Kafka Stream背景</h2><h4 id="Kafka-Stream是什么"><a href="#Kafka-Stream是什么" class="headerlink" title="Kafka Stream是什么"></a>Kafka Stream是什么</h4><p>&ensp;&ensp;&ensp;&ensp;Kafka Stream是Apache Kafka从0.10版本引入的一个新Feature。它是提供了对存储于Kafka内的数据进行流式处理和分析的功能。</p>
<p>Kafka Stream的特点如下：</p>
<ul>
<li><p>Kafka Stream提供了一个非常简单而轻量的Library，它可以非常方便地嵌入任意Java应用中，也可以任意方式打包和部署</p>
</li>
<li><p>除了Kafka外，无任何外部依赖</p>
</li>
<li><p>充分利用Kafka分区机制实现水平扩展和顺序性保证</p>
</li>
<li><p>通过可容错的state store实现高效的状态操作（如windowed join和aggregation）</p>
</li>
<li><p>支持正好一次处理语义</p>
</li>
<li><p>提供记录级的处理能力，从而实现毫秒级的低延迟</p>
</li>
<li><p>支持基于事件时间的窗口操作，并且可处理晚到的数据（late arrival of records）</p>
</li>
<li><p>同时提供底层的处理原语Processor（类似于Storm的spout和bolt），以及高层抽象的DSL（类似于Spark的map/group/reduce）</p>
<h4 id="什么是流式计算"><a href="#什么是流式计算" class="headerlink" title="什么是流式计算"></a>什么是流式计算</h4><p>&ensp;&ensp;&ensp;&ensp;一般流式计算会与批量计算相比较。在流式计算模型中，输入是持续的，可以认为在时间上是无界的，也就意味着，永远拿不到全量数据去做计算。同时，计算结果是持续输出的，也即计算结果在时间上也是无界的。流式计算一般对实时性要求较高，同时一般是先定义目标计算，然后数据到来之后将计算逻辑应用于数据。同时为了提高计算效率，往往尽可能采用增量计算代替全量计算。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/1.jpg" alt="Kafka_Stream"><br>&ensp;&ensp;&ensp;&ensp;批量处理模型中，一般先有全量数据集，然后定义计算逻辑，并将计算应用于全量数据。特点是全量计算，并且计算结果一次性全量输出。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/2.jpg" alt="Kafka_Stream"></p>
<h4 id="为什么要有Kafka-Stream"><a href="#为什么要有Kafka-Stream" class="headerlink" title="为什么要有Kafka Stream"></a>为什么要有Kafka Stream</h4><p>&ensp;&ensp;&ensp;&ensp;当前已经有非常多的流式处理系统，最知名且应用最多的开源流式处理系统有Spark Streaming和Apache Storm。Apache Storm发展多年，应用广泛，提供记录级别的处理能力，当前也支持SQL on Stream。而Spark Streaming基于Apache Spark，可以非常方便与图计算，SQL处理等集成，功能强大，对于熟悉其它Spark应用开发的用户而言使用门槛低。另外，目前主流的Hadoop发行版，如MapR，Cloudera和Hortonworks，都集成了Apache Storm和Apache Spark，使得部署更容易。</p>
</li>
</ul>
<p>&ensp;&ensp;&ensp;&ensp;既然Apache Spark与Apache Storm拥用如此多的优势，那为何还需要Kafka Stream呢？笔者认为主要有如下原因。</p>
<p>&ensp;&ensp;&ensp;&ensp;第一，Spark和Storm都是流式处理框架，而Kafka Stream提供的是一个基于Kafka的流式处理类库。框架要求开发者按照特定的方式去开发逻辑部分，供框架调用。开发者很难了解框架的具体运行方式，从而使得调试成本高，并且使用受限。而Kafka Stream作为流式处理类库，直接提供具体的类给开发者调用，整个应用的运行方式主要由开发者控制，方便使用和调试。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/3.jpg" alt="Kafka_Stream"><br>&ensp;&ensp;&ensp;&ensp;第二，虽然Cloudera与Hortonworks方便了Storm和Spark的部署，但是这些框架的部署仍然相对复杂。而Kafka Stream作为类库，可以非常方便的嵌入应用程序中，它对应用的打包和部署基本没有任何要求。更为重要的是，Kafka Stream充分利用了Kafka的分区机制和Consumer的Rebalance机制，使得Kafka Stream可以非常方便的水平扩展，并且各个实例可以使用不同的部署方式。具体来说，每个运行Kafka Stream的应用程序实例都包含了Kafka Consumer实例，多个同一应用的实例之间并行处理数据集。而不同实例之间的部署方式并不要求一致，比如部分实例可以运行在Web容器中，部分实例可运行在Docker或Kubernetes中。</p>
<p>&ensp;&ensp;&ensp;&ensp;第三，就流式处理系统而言，基本都支持Kafka作为数据源。例如Storm具有专门的kafka-spout，而Spark也提供专门的spark-streaming-kafka模块。事实上，Kafka基本上是主流的流式处理系统的标准数据源。换言之，大部分流式系统中都已部署了Kafka，此时使用Kafka Stream的成本非常低。</p>
<p>&ensp;&ensp;&ensp;&ensp;第四，使用Storm或Spark Streaming时，需要为框架本身的进程预留资源，如Storm的supervisor和Spark on YARN的node manager。即使对于应用实例而言，框架本身也会占用部分资源，如Spark Streaming需要为shuffle和storage预留内存。</p>
<p>&ensp;&ensp;&ensp;&ensp;第五，由于Kafka本身提供数据持久化，因此Kafka Stream提供滚动部署和滚动升级以及重新计算的能力。</p>
<p>&ensp;&ensp;&ensp;&ensp;第六，由于Kafka Consumer Rebalance机制，Kafka Stream可以在线动态调整并行度。</p>
<h2 id="Kafka-Stream架构"><a href="#Kafka-Stream架构" class="headerlink" title="Kafka Stream架构"></a>Kafka Stream架构</h2><h4 id="Kafka-Stream整体架构："><a href="#Kafka-Stream整体架构：" class="headerlink" title="Kafka Stream整体架构："></a>Kafka Stream整体架构：</h4><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/4.jpg" alt="Kafka_Stream"></p>
<p>&ensp;&ensp;&ensp;&ensp;目前（Kafka 0.11.0.0）Kafka Stream的数据源只能如上图所示是Kafka。但是处理结果并不一定要如上图所示输出到Kafka。实际上KStream和Ktable的实例化都需要指定Topic。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/15.png" alt="Kafka_Stream"></p>
<p>&ensp;&ensp;&ensp;&ensp;另外，上图中的Consumer和Producer并不需要开发者在应用中显示实例化，而是由Kafka Stream根据参数隐式实例化和管理，从而降低了使用门槛。开发者只需要专注于开发核心业务逻辑，也即上图中Task内的部分。</p>
<h4 id="Processor-Topology"><a href="#Processor-Topology" class="headerlink" title="Processor Topology"></a>Processor Topology</h4><p>&ensp;&ensp;&ensp;&ensp;基于Kafka Stream的流式应用的业务逻辑全部通过一个被称为Processor Topology的地方执行。它与Storm的Topology和Spark的DAG类似，都定义了数据在各个处理单元（在Kafka Stream中被称作Processor）间的流动方式，或者说定义了数据的处理逻辑。</p>
<p>&ensp;&ensp;&ensp;&ensp;下面是一个Processor的示例，它实现了Word Count功能，并且每秒输出一次结果。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/5.jpg" alt="Kafka_Stream"></p>
<p>从上述代码中可见</p>
<pre><code>process定义了对每条记录的处理逻辑，也印证了Kafka可具有记录级的数据处理能力。

context.scheduler定义了punctuate被执行的周期，从而提供了实现窗口操作的能力。

context.getStateStore提供的状态存储为有状态计算（如窗口，聚合）提供了可能。
</code></pre><h4 id="Kafka-Stream并行模型："><a href="#Kafka-Stream并行模型：" class="headerlink" title="Kafka Stream并行模型："></a>Kafka Stream并行模型：</h4><p>&ensp;&ensp;&ensp;&ensp;Kafka Stream的并行模型中，最小粒度为Task，而每个Task包含一个特定子Topology的所有Processor。因此每个Task所执行的代码完全一样，唯一的不同在于所处理的数据集互补。这一点跟Storm的Topology完全不一样。Storm的Topology的每一个Task只包含一个Spout或Bolt的实例。因此Storm的一个Topology内的不同Task之间需要通过网络通信传递数据，而Kafka Stream的Task包含了完整的子Topology，所以Task之间不需要传递数据，也就不需要网络通信。这一点降低了系统复杂度，也提高了处理效率。</p>
<p>&ensp;&ensp;&ensp;&ensp;如果某个Stream的输入Topic有多个(比如2个Topic，1个Partition数为4，另一个Partition数为3)，则总的Task数等于Partition数最多的那个Topic的Partition数（max(4,3)=4）。这是因为Kafka Stream使用了Consumer的Rebalance机制，每个Partition对应一个Task。</p>
<p>&ensp;&ensp;&ensp;&ensp;下图展示了在一个进程（Instance）中以2个Topic（Partition数均为4）为数据源的Kafka Stream应用的并行模型。从图中可以看到，由于Kafka Stream应用的默认线程数为1，所以4个Task全部在一个线程中运行。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/6.jpg" alt="Kafka_Stream"></p>
<p>&ensp;&ensp;&ensp;&ensp;为了充分利用多线程的优势，可以设置Kafka Stream的线程数。下图展示了线程数为2时的并行模型。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/7.jpg" alt="Kafka_Stream"></p>
<p>&ensp;&ensp;&ensp;&ensp;前文有提到，Kafka Stream可被嵌入任意Java应用（理论上基于JVM的应用都可以）中，下图展示了在同一台机器的不同进程中同时启动同一Kafka Stream应用时的并行模型。注意，这里要保证两个进程的StreamsConfig.APPLICATION_ID_CONFIG完全一样。因为Kafka Stream将APPLICATION_ID_CONFI作为隐式启动的Consumer的Group ID。只有保证APPLICATION_ID_CONFI相同，才能保证这两个进程的Consumer属于同一个Group，从而可以通过Consumer Rebalance机制拿到互补的数据集。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/8.jpg" alt="Kafka_Stream"></p>
<p>&ensp;&ensp;&ensp;&ensp;既然实现了多进程部署，可以以同样的方式实现多机器部署。该部署方式也要求所有进程的APPLICATION_ID_CONFIG完全一样。从图上也可以看到，每个实例中的线程数并不要求一样。但是无论如何部署，Task总数总会保证一致。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/9.jpg" alt="Kafka_Stream"></p>
<p>注意：Kafka Stream的并行模型，非常依赖于《Kafka设计解析（一）- Kafka背景及架构介绍》一文中介绍的Kafka分区机制和《Kafka设计解析（四）- Kafka Consumer设计解析》中介绍的Consumer的Rebalance机制。强烈建议不太熟悉这两种机制的朋友，先行阅读这两篇文章。</p>
<p>这里对比一下Kafka Stream的Processor Topology与Storm的Topology。</p>
<pre><code>Storm的Topology由Spout和Bolt组成，Spout提供数据源，而Bolt提供计算和数据导出。Kafka Stream的Processor Topology完全由Processor组成，因为它的数据固定由Kafka的Topic提供。

Storm的不同Bolt运行在不同的Executor中，很可能位于不同的机器，需要通过网络通信传输数据。而Kafka Stream的Processor Topology的不同Processor完全运行于同一个Task中，也就完全处于同一个线程，无需网络通信。

Storm的Topology可以同时包含Shuffle部分和非Shuffle部分，并且往往一个Topology就是一个完整的应用。而Kafka Stream的一个物理Topology只包含非Shuffle部分，而Shuffle部分需要通过through操作显示完成，该操作将一个大的Topology分成了2个子Topology。

Storm的Topology内，不同Bolt/Spout的并行度可以不一样，而Kafka Stream的子Topology内，所有Processor的并行度完全一样。

Storm的一个Task只包含一个Spout或者Bolt的实例，而Kafka Stream的一个Task包含了一个子Topology的所有Processor。
</code></pre><h4 id="KTable-vs-KStream"><a href="#KTable-vs-KStream" class="headerlink" title="KTable vs. KStream"></a>KTable vs. KStream</h4><p>&ensp;&ensp;&ensp;&ensp;KTable和KStream是Kafka Stream中非常重要的两个概念，它们是Kafka实现各种语义的基础。因此这里有必要分析下二者的区别。</p>
<p>&ensp;&ensp;&ensp;&ensp;KStream是一个数据流，可以认为所有记录都通过Insert only的方式插入进这个数据流里。而KTable代表一个完整的数据集，可以理解为数据库中的表。由于每条记录都是Key-Value对，这里可以将Key理解为数据库中的Primary Key，而Value可以理解为一行记录。可以认为KTable中的数据都是通过Update only的方式进入的。也就意味着，如果KTable对应的Topic中新进入的数据的Key已经存在，那么从KTable只会取出同一Key对应的最后一条数据，相当于新的数据更新了旧的数据。</p>
<p>&ensp;&ensp;&ensp;&ensp;以下图为例，假设有一个KStream和KTable，基于同一个Topic创建，并且该Topic中包含如下图所示5条数据。此时遍历KStream将得到与Topic内数据完全一样的所有5条数据，且顺序不变。而此时遍历KTable时，因为这5条记录中有3个不同的Key，所以将得到3条记录，每个Key对应最新的值，并且这三条数据之间的顺序与原来在Topic中的顺序保持一致。这一点与Kafka的日志compact相同。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/10.jpg" alt="Kafka_Stream"></p>
<p>&ensp;&ensp;&ensp;&ensp;此时如果对该KStream和KTable分别基于key做Group，对Value进行Sum，得到的结果将会不同。对KStream的计算结果是&lt;Jack，4&gt;，&lt;Lily，7&gt;，&lt;Mike，4&gt;。而对Ktable的计算结果是&lt;Mike，4&gt;，&lt;Jack，3&gt;，&lt;Lily，5&gt;。</p>
<h4 id="State-store"><a href="#State-store" class="headerlink" title="State store"></a>State store</h4><p>&ensp;&ensp;&ensp;&ensp;流式处理中，部分操作是无状态的，例如过滤操作（Kafka Stream DSL中用filer方法实现）。而部分操作是有状态的，需要记录中间状态，如Window操作和聚合计算。State store被用来存储中间状态。它可以是一个持久化的Key-Value存储，也可以是内存中的HashMap，或者是数据库。Kafka提供了基于Topic的状态存储。</p>
<p>&ensp;&ensp;&ensp;&ensp;Topic中存储的数据记录本身是Key-Value形式的，同时Kafka的log compaction机制可对历史数据做compact操作，保留每个Key对应的最后一个Value，从而在保证Key不丢失的前提下，减少总数据量，从而提高查询效率。</p>
<p>&ensp;&ensp;&ensp;&ensp;构造KTable时，需要指定其state store name。默认情况下，该名字也即用于存储该KTable的状态的Topic的名字，遍历KTable的过程，实际就是遍历它对应的state store，或者说遍历Topic的所有key，并取每个Key最新值的过程。为了使得该过程更加高效，默认情况下会对该Topic进行compact操作。</p>
<p>&ensp;&ensp;&ensp;&ensp;另外，除了KTable，所有状态计算，都需要指定state store name，从而记录中间状态。</p>
<h2 id="Kafka-Stream如何解决流式系统中关键问题"><a href="#Kafka-Stream如何解决流式系统中关键问题" class="headerlink" title="Kafka Stream如何解决流式系统中关键问题"></a>Kafka Stream如何解决流式系统中关键问题</h2><h4 id="时间"><a href="#时间" class="headerlink" title="时间"></a>时间</h4><p>在流式数据处理中，时间是数据的一个非常重要的属性。从Kafka 0.10开始，每条记录除了Key和Value外，还增加了timestamp属性。目前Kafka Stream支持三种时间</p>
<pre><code>事件发生时间。事件发生的时间，包含在数据记录中。发生时间由Producer在构造ProducerRecord时指定。并且需要Broker或者Topic将message.timestamp.type设置为CreateTime（默认值）才能生效。

消息接收时间，也即消息存入Broker的时间。当Broker或Topic将message.timestamp.type设置为LogAppendTime时生效。此时Broker会在接收到消息后，存入磁盘前，将其timestamp属性值设置为当前机器时间。一般消息接收时间比较接近于事件发生时间，部分场景下可代替事件发生时间。

消息处理时间，也即Kafka Stream处理消息时的时间。
</code></pre><p>注：Kafka Stream允许通过实现org.apache.kafka.streams.processor.TimestampExtractor接口自定义记录时间。</p>
<h4 id="窗口"><a href="#窗口" class="headerlink" title="窗口"></a>窗口</h4><p>&ensp;&ensp;&ensp;&ensp;前文提到，流式数据是在时间上无界的数据。而聚合操作只能作用在特定的数据集，也即有界的数据集上。因此需要通过某种方式从无界的数据集上按特定的语义选取出有界的数据。窗口是一种非常常用的设定计算边界的方式。不同的流式处理系统支持的窗口类似，但不尽相同。</p>
<h4 id="Kafka-Stream支持的窗口如下。"><a href="#Kafka-Stream支持的窗口如下。" class="headerlink" title="Kafka Stream支持的窗口如下。"></a>Kafka Stream支持的窗口如下。</h4><pre><code>Hopping Time Window 该窗口定义如下图所示。它有两个属性，一个是Window size，一个是Advance interval。Window size指定了窗口的大小，也即每次计算的数据集的大小。而Advance interval定义输出的时间间隔。一个典型的应用场景是，每隔5秒钟输出一次过去1个小时内网站的PV或者UV。
</code></pre><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/11.gif" alt="Kafka_Stream"></p>
<pre><code>Tumbling Time Window该窗口定义如下图所示。可以认为它是Hopping Time Window的一种特例，也即Window size和Advance interval相等。它的特点是各个Window之间完全不相交。
</code></pre><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/12.gif" alt="Kafka_Stream"></p>
<pre><code>Sliding Window该窗口只用于2个KStream进行Join计算时。该窗口的大小定义了Join两侧KStream的数据记录被认为在同一个窗口的最大时间差。假设该窗口的大小为5秒，则参与Join的2个KStream中，记录时间差小于5的记录被认为在同一个窗口中，可以进行Join计算。

Session Window该窗口用于对Key做Group后的聚合操作中。它需要对Key做分组，然后对组内的数据根据业务需求定义一个窗口的起始点和结束点。一个典型的案例是，希望通过Session Window计算某个用户访问网站的时间。对于一个特定的用户（用Key表示）而言，当发生登录操作时，该用户（Key）的窗口即开始，当发生退出操作或者超时时，该用户（Key）的窗口即结束。窗口结束时，可计算该用户的访问时间或者点击次数等。
</code></pre><h4 id="join"><a href="#join" class="headerlink" title="join"></a>join</h4><p>&ensp;&ensp;&ensp;&ensp;Kafka Stream由于包含KStream和Ktable两种数据集，因此提供如下Join计算</p>
<pre><code>KTable Join KTable 结果仍为KTable。任意一边有更新，结果KTable都会更新。

KStream Join KStream 结果为KStream。必须带窗口操作，否则会造成Join操作一直不结束。

KStream Join KTable / GlobakKTable 结果为KStream。只有当KStream中有新数据时，才会触发Join计算并输出结果。KStream无新数据时，KTable的更新并不会触发Join计算，也不会输出数据。并且该更新只对下次Join生效。一个典型的使用场景是，KStream中的订单信息与KTable中的用户信息做关联计算。
</code></pre><p>&ensp;&ensp;&ensp;&ensp;对于Join操作，如果要得到正确的计算结果，需要保证参与Join的KTable或KStream中Key相同的数据被分配到同一个Task。具体方法是</p>
<pre><code>参与Join的KTable或KStream的Key类型相同（实际上，业务含意也应该相同）

参与Join的KTable或KStream对应的Topic的Partition数相同

Partitioner策略的最终结果等效（实现不需要完全一样，只要效果一样即可），也即Key相同的情况下，被分配到ID相同的Partition内
</code></pre><p>&ensp;&ensp;&ensp;&ensp;如果上述条件不满足，可通过调用如下方法使得它满足上述条件。</p>
<blockquote>
<p>KStream&lt;K, V&gt; through(Serde<k> keySerde, Serde<v> valSerde, StreamPartitioner&lt;K, V&gt; partitioner, String topic)</v></k></p>
</blockquote>
<h4 id="合与乱序处理"><a href="#合与乱序处理" class="headerlink" title="合与乱序处理"></a>合与乱序处理</h4><p>&ensp;&ensp;&ensp;&ensp;聚合操作可应用于KStream和KTable。当聚合发生在KStream上时必须指定窗口，从而限定计算的目标数据集。</p>
<p>&ensp;&ensp;&ensp;&ensp;需要说明的是，聚合操作的结果肯定是KTable。因为KTable是可更新的，可以在晚到的数据到来时（也即发生数据乱序时）更新结果KTable。</p>
<p>&ensp;&ensp;&ensp;&ensp;这里举例说明。假设对KStream以5秒为窗口大小，进行Tumbling Time Window上的Count操作。并且KStream先后出现时间为1秒, 3秒, 5秒的数据，此时5秒的窗口已达上限，Kafka Stream关闭该窗口，触发Count操作并将结果3输出到KTable中（假设该结果表示为&lt;1-5,3&gt;）。若1秒后，又收到了时间为2秒的记录，由于1-5秒的窗口已关闭，若直接抛弃该数据，则可认为之前的结果&lt;1-5,3&gt;不准确。而如果直接将完整的结果&lt;1-5,4&gt;输出到KStream中，则KStream中将会包含该窗口的2条记录，&lt;1-5,3&gt;, &lt;1-5,4&gt;，也会存在肮数据。因此Kafka Stream选择将聚合结果存于KTable中，此时新的结果&lt;1-5,4&gt;会替代旧的结果&lt;1-5,3&gt;。用户可得到完整的正确的结果。</p>
<p>&ensp;&ensp;&ensp;&ensp;这种方式保证了数据准确性，同时也提高了容错性。</p>
<p>&ensp;&ensp;&ensp;&ensp;但需要说明的是，Kafka Stream并不会对所有晚到的数据都重新计算并更新结果集，而是让用户设置一个retention period，将每个窗口的结果集在内存中保留一定时间，该窗口内的数据晚到时，直接合并计算，并更新结果KTable。超过retention period后，该窗口结果将从内存中删除，并且晚到的数据即使落入窗口，也会被直接丢弃。</p>
<h4 id="容错"><a href="#容错" class="headerlink" title="容错"></a>容错</h4><p>Kafka Stream从如下几个方面进行容错</p>
<pre><code>高可用的Partition保证无数据丢失。每个Task计算一个Partition，而Kafka数据复制机制保证了Partition内数据的高可用性，故无数据丢失风险。同时由于数据是持久化的，即使任务失败，依然可以重新计算。

状态存储实现快速故障恢复和从故障点继续处理。对于Join和聚合及窗口等有状态计算，状态存储可保存中间状态。即使发生Failover或Consumer Rebalance，仍然可以通过状态存储恢复中间状态，从而可以继续从Failover或Consumer Rebalance前的点继续计算。

KTable与retention period提供了对乱序数据的处理能力。
</code></pre><h2 id="Kafka-Stream应用示例"><a href="#Kafka-Stream应用示例" class="headerlink" title="Kafka Stream应用示例"></a>Kafka Stream应用示例</h2><p>&ensp;&ensp;&ensp;&ensp;下面结合一个案例来讲解如何开发Kafka Stream应用。本例完整代码可从作者Github获取。</p>
<p>&ensp;&ensp;&ensp;&ensp;订单KStream（名为orderStream），底层Topic的Partition数为3，Key为用户名，Value包含用户名，商品名，订单时间，数量。用户KTable（名为userTable），底层Topic的Partition数为3，Key为用户名，Value包含性别，地址和年龄。商品KTable（名为itemTable），底层Topic的Partition数为6，Key为商品名，价格，种类和产地。现在希望计算每小时购买产地与自己所在地相同的用户总数。</p>
<p>&ensp;&ensp;&ensp;&ensp;首先由于希望使用订单时间，而它包含在orderStream的Value中，需要通过提供一个实现TimestampExtractor接口的类从orderStream对应的Topic中抽取出订单时间。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/13.png" alt="Kafka_Stream"></p>
<p>&ensp;&ensp;&ensp;&ensp;接着通过将orderStream与userTable进行Join，来获取订单用户所在地。由于二者对应的Topic的Partition数相同，且Key都为用户名，再假设Producer往这两个Topic写数据时所用的Partitioner实现相同，则此时上文所述Join条件满足，可直接进行Join。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/18/分布式流处理新贵Kafka-Stream/14.png" alt="Kafka_Stream"></p>
<p>&ensp;&ensp;&ensp;&ensp;从上述代码中，可以看到，Join时需要指定如何从参与Join双方的记录生成结果记录的Value。Key不需要指定，因为结果记录的Key与Join Key相同，故无须指定。Join结果存于名为orderUserStream的KStream中。</p>
<p>&ensp;&ensp;&ensp;&ensp;接下来需要将orderUserStream与itemTable进行Join，从而获取商品产地。此时orderUserStream的Key仍为用户名，而itemTable对应的Topic的Key为产品名，并且二者的Partition数不一样，因此无法直接Join。此时需要通过through方法，对其中一方或双方进行重新分区，使得二者满足Join条件。这一过程相当于Spark的Shuffle过程和Storm的FieldGrouping。</p>
<blockquote>
<p>orderUserStrea<br>       .through(<br>          // Key的序列化方式<br>         Serdes.String(),<br>         // Value的序列化方式<br>         SerdesFactory.serdFrom(OrderUser.class),<br>         // 重新按照商品名进行分区，具体取商品名的哈希值，然后对分区数取模<br>         (String key, OrderUser orderUser, int numPartitions) -&gt; (orderUser.getItemName().hashCode() &amp; 0x7FFFFFFF) % numPartitions,<br>         “orderuser-repartition-by-item”)<br>     .leftJoin(itemTable, (OrderUser orderUser, Item item) -&gt; OrderUserItem.fromOrderUser(orderUser, item), Serdes.String(), SerdesFactory.serdFrom(OrderUser.class))</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;从上述代码可见，through时需要指定Key的序列化器，Value的序列化器，以及分区方式和结果集所在的Topic。这里要注意，该Topic（orderuser-repartition-by-item）的Partition数必须与itemTable对应Topic的Partition数相同，并且through使用的分区方法必须与iteamTable对应Topic的分区方式一样。经过这种through操作，orderUserStream与itemTable满足了Join条件，可直接进行Join。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><pre><code>Kafka Stream的并行模型完全基于Kafka的分区机制和Rebalance机制，实现了在线动态调整并行度

同一Task包含了一个子Topology的所有Processor，使得所有处理逻辑都在同一线程内完成，避免了不必的网络通信开销，从而提高了效率。

through方法提供了类似Spark的Shuffle机制，为使用不同分区策略的数据提供了Join的可能

log compact提高了基于Kafka的state store的加载效率

state store为状态计算提供了可能

基于offset的计算进度管理以及基于state store的中间状态管理为发生Consumer rebalance或Failover时从断点处继续处理提供了可能，并为系统容错性提供了保障

KTable的引入，使得聚合计算拥用了处理乱序问题的能力
</code></pre>
      
    </div>
    
    <div class="article-info article-info-index">
      
      

      
    <div class="article-tag tagcloud">
        <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Kafka/">Kafka</a></li></ul>
    </div>

      
      <div class="clearfix"></div>
    </div>
    
  </div>
  
</article>









  
    <article id="post-Redis命令学习与集群搭建，包含Redis3-X的集群搭建" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/" class="article-date">
      <time datetime="2018-09-15T09:48:40.000Z" itemprop="datePublished">2018-09-15</time>
</a>


    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/">Redis命令学习与集群搭建，包含Redis3.X的集群搭建</a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
          
        <h2 id="一、Redis的安装"><a href="#一、Redis的安装" class="headerlink" title="一、Redis的安装"></a>一、Redis的安装</h2><h3 id="解压安装包"><a href="#解压安装包" class="headerlink" title="解压安装包"></a>解压安装包</h3><p><table><tr><td bgcolor="#FF4500">[root@node01 sxt]# tar xf redis-2.8.18.tar.gz</td></tr></table>  </p>
<h3 id="安装编译器"><a href="#安装编译器" class="headerlink" title="安装编译器"></a>安装编译器</h3><p><table><tr><td bgcolor="#FF4500">[root@node01 sxt]# yum install gcc tcl -y </td></tr></table>  </p>
<h3 id="执行编译"><a href="#执行编译" class="headerlink" title="执行编译"></a>执行编译</h3><p><table><tr><td bgcolor="#FF4500">[root@node01 sxt]# cd redis-2.8.18/</td></tr> </table></p>
<p><tr><td bgcolor="#FF4500">[root@node01 redis-2.8.18]# make</td></tr></p>
<h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p><table><tr><td bgcolor="#FF4500">[root@node01 redis-2.8.18]# make PREFIX=/opt/sxt/redis install  </td></tr></table>   </p>
<h3 id="配置环境变量"><a href="#配置环境变量" class="headerlink" title="配置环境变量"></a>配置环境变量</h3><p><table><tr><td bgcolor="#FF4500">[root@node01 redis]# vi + /etc/profile </td></tr></table></p>
<p><tr><td bgcolor="#FF4500">export REDIS_HOME=/opt/sxt/redis export PATH=\$PATH:\$REDIS_HOME/bin </td></tr></p>
<p><tr><td bgcolor="#FF4500">[root@node01 redis]# . /etc/profile </td></tr> </p>
<h3 id="进入编译包的-util-目录下，进一步完善安装配置"><a href="#进入编译包的-util-目录下，进一步完善安装配置" class="headerlink" title="进入编译包的 util 目录下，进一步完善安装配置"></a>进入编译包的 util 目录下，进一步完善安装配置</h3><p><table><tr><td bgcolor="#FF4500"> [root@node01 redis]# cd /opt/sxt/redis-2.8.18/utils   </td></tr><br></table></p>
<p><table><tr><td bgcolor="#FF4500">[root@node01 utils]# ./install_server.sh  </td></tr></table><br>    Welcome to the redis service installer<br>    This script will help you easily set up a running redis server   </p>
<pre><code>#如果不用默认配置，需要手动输入端口号，我们这里就用默认端口，按 Enter   
Please select the redis port for this instance: [6379]    
Selecting default: 6379 
#选择默认的配置文件的名称，按 Enter，使用别的路径和名称需要手动指定   
Please select the redis config file name [/etc/redis/6379.conf]   
Selected default - /etc/redis/6379.conf   
#日志存放位置，处理同上    
Please select the redis log file name [/var/log/redis_6379.log]   
Selected default - /var/log/redis_6379.log   
#持久化数据目录，处理同上   
Please select the data directory for this instance [/var/lib/redis/6379]   
Selected default - /var/lib/redis/6379 
#指定 redis 的命令执行路径，因为已经在环境变量中配置过，这里不再做处理   
Please select the redis executable path [/opt/sxt/redis/bin/redis-server] Selected config:   
Port           : 6379   
Config file    : /etc/redis/6379.conf  
Log file       : /var/log/redis_6379.log   
Data dir       : /var/lib/redis/6379  
Executable     : /opt/sxt/redis/bin/redis-server   
Cli Executable : /opt/sxt/redis/bin/redis-cli     
Is this ok? Then press ENTER to go on or Ctrl-C to abort. # Enter     
Copied /tmp/6379.conf =&gt; /etc/init.d/redis_6379 Installing service...   
Successfully added to chkconfig!   
Successfully added to runlevels 345!   
Starting Redis server...   
Installation successful!   
#可以多次执行 install_server.sh 脚本安装不同端口下的 redis 服务   
</code></pre><h3 id="进入-redis-客户端"><a href="#进入-redis-客户端" class="headerlink" title="进入 redis 客户端"></a>进入 redis 客户端</h3><ul>
<li>在安装目录下执行 redis-cli，不是编译包下<br><table><tr><td bgcolor="#FF4500">[root@node01 bin]# redis-cli  </td></tr></table> <h2 id="二、Redis-数据模型"><a href="#二、Redis-数据模型" class="headerlink" title="二、Redis 数据模型"></a>二、Redis 数据模型</h2>&ensp;&ensp;&ensp;&ensp;Redis 是一个开源的(BSD 协议)，使用 ANSI C 编写，基于内存的且支持持久化，高性能的 Key-Value 的 NoSQL数据库。支持数据结构类型丰富，有如字符串(strings)，散列(hashes)，列表(lists)，集合(sets)，有序集合(sorted sets)与范围查询,bitmaps， hyperloglogs 和地理空间(geospatial)索引半径查询。具有丰富的支持主流语言的客户端，C、C++、Python、Erlang、R、C#、Java、PHP、Objective-C、Perl、Ruby、Scala、Go、JavaScript 等，主要用于：缓存(StackOverFlow)、数据库(微博)、消息中间件(微博)。<br>&ensp;&ensp;&ensp;&ensp;Redis key 值是二进制安全的，这意味着可以用任何二进制序列作为 key 值，从形如”foo”的简单字符串到一个 JPEG 文件的内容都可以。空字符串也是有效 key 值<br>&ensp;&ensp;&ensp;&ensp;Key 取值原则 ：<br><table><tr><td bgcolor="#FF4500">键值不需要太长，消耗内存，且在数据中查找这类键值的计算成本较高键值不宜过短，可读性较差  </td></tr></table><br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/redis数据模型.jpg" alt="Redis数据模型"></li>
</ul>
<h3 id="String"><a href="#String" class="headerlink" title="String"></a>String</h3><p>&ensp;&ensp;&ensp;&ensp;字符串是一种最基本的 Redis 值类型。Redis 字符串是二进制安全的，这意味着一个Redis 字符串能包含任意类型的数据，例如：一张 JPEG 格式的图片或者一个序列化的 Ruby 对象。一个字符串类型的值最多能存储 512M 字节的内容。<br>Key：string<br>Value：string </p>
<h4 id="1-String常用命令"><a href="#1-String常用命令" class="headerlink" title="1.String常用命令"></a>1.String常用命令</h4><p> &ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/String_01.png" alt="01string"><br> &ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/String_02.png" alt="02string"><br> &ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/String_03.png" alt="03string"><br>  &ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/String_04.png" alt="04string"><br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/String_05.png" alt="05string"></p>
<h4 id="2-与key有关的命令"><a href="#2-与key有关的命令" class="headerlink" title="2.与key有关的命令"></a>2.与key有关的命令</h4><h5 id="TYPE-和-OBJECT-encoding"><a href="#TYPE-和-OBJECT-encoding" class="headerlink" title="TYPE 和 OBJECT encoding"></a>TYPE 和 OBJECT encoding</h5><p>&ensp;&ensp;&ensp;&ensp;&ensp;TYPE 返回的是 redis 支持的常规数据类型，OBJECT encoding 返回的是，value 底层存储的数据结构类型</p>
<blockquote>
<p>127.0.0.1:6379&gt; TYPE key5<br>none<br>127.0.0.1:6379&gt; TYPE key3<br>string<br>127.0.0.1:6379&gt; OBJECT encoding key3<br>“int” </p>
</blockquote>
<h5 id="keys查询命令"><a href="#keys查询命令" class="headerlink" title="keys查询命令"></a>keys查询命令</h5><blockquote>
<p>127.0.0.1:6379&gt; KEYS <em><br>1)    “key2”<br>2)    “key4”<br>3)    “key3”<br>4)    “key1”<br>127.0.0.1:6379&gt; KEYS </em>1<br>1) “key1” </p>
</blockquote>
<h5 id="key是否存在"><a href="#key是否存在" class="headerlink" title="key是否存在"></a>key是否存在</h5><blockquote>
<p>127.0.0.1:6379&gt; EXISTS key1<br>(integer) 1</p>
</blockquote>
<h5 id="Rename"><a href="#Rename" class="headerlink" title="Rename"></a>Rename</h5><blockquote>
<p>127.0.0.1:6379&gt; RENAME key1 key1_new<br>OK<br>127.0.0.1:6379&gt; get key1_new<br>“helloredis”<br>127.0.0.1:6379&gt; RENAMENX key2 key1_new<br>(integer) 0<br>key1_new 已经存在，所以 RENAMENX 命令不能为 key2 重命名为 key1_new</p>
</blockquote>
<h5 id="DEL"><a href="#DEL" class="headerlink" title="DEL"></a>DEL</h5><blockquote>
<p>127.0.0.1:6379&gt; DEL key2<br>(integer) 1<br>127.0.0.1:6379&gt; get key2<br>(nil)</p>
</blockquote>
<h4 id="3-Bitmap"><a href="#3-Bitmap" class="headerlink" title="3.Bitmap"></a>3.Bitmap</h4><h5 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h5><p>&ensp;&ensp;&ensp;&ensp;&ensp;位图不是真正的数据类型，它是定义在字符串类型中，一个字符串类型的值最多能存储 512M 字节的内容，位上限：2^(9+10+10+3)=2^32b</p>
<blockquote>
<p>127.0.0.1:6379&gt; setbit key6 1 1<br>(integer) 0<br>127.0.0.1:6379&gt; setbit key6 7 1<br>(integer) 0<br>127.0.0.1:6379&gt; get key6 # 01000001 –&gt; 65 –&gt; A<br>“A”<br>127.0.0.1:6379&gt; getbit key6 1<br>(integer) 1<br>返回指定值 0 或者 1(这里是 1)在指定区间上[第 0 个字节到第 1 个字节]中第一次出现的位置（整个二进制序列中的位数）<br>127.0.0.1:6379&gt; bitpos key6 1 0 1<br>(integer) 1<br>返回指定区间（字节区间）上值为 1 的个数<br>127.0.0.1:6379&gt; bitcount key6 0 0<br>(integer) 2</p>
</blockquote>
<h5 id="位操作"><a href="#位操作" class="headerlink" title="位操作"></a>位操作</h5><p>&ensp;&ensp;&ensp;&ensp;&ensp;对一个或多个保存二进制位的字符串 key 进行位元操作，并将结果保存到 destkey上。<br>&ensp;&ensp;&ensp;&ensp;&ensp;operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种<br>&ensp;&ensp;&ensp;&ensp;&ensp;BITOP AND destkey key [key …] ，对一个或多个 key 求逻辑与，并将结果保存到 destkey<br>&ensp;&ensp;&ensp;&ensp;&ensp;BITOP OR destkey key [key …] ，对一个或多个 key 求逻辑或，并将结果保存到 destkey<br>&ensp;&ensp;&ensp;&ensp;&ensp;BITOP XOR destkey key [key …] ，对一个或多个 key 求逻辑异或，并将结果保存到 destkey<br>&ensp;&ensp;&ensp;&ensp;&ensp;BITOP NOT destkey key ，对给定 key 求逻辑非，并将结果保存到 destkey<br>&ensp;&ensp;&ensp;&ensp;&ensp;除了 NOT 操作之外，其他操作都可以接受一个或多个 key 作为输入<br>&ensp;&ensp;&ensp;&ensp;&ensp;当 BITOP 处理不同长度的字符串时，较短的那个字符串所缺少的部分会被看作 0<br>&ensp;&ensp;&ensp;&ensp;&ensp;空的 key 也被看作是包含 0 的字符串序列</p>
<blockquote>
<p>127.0.0.1:6379&gt; setbit key7 1 1<br>(integer) 0<br>127.0.0.1:6379&gt; setbit key7 3 1<br>(integer) 0<br>127.0.0.1:6379&gt; setbit key7 5 1<br>(integer) 0<br>127.0.0.1:6379&gt; setbit key7 6 1<br>(integer) 0<br>127.0.0.1:6379&gt; setbit key7 7 1<br>(integer) 0<br>127.0.0.1:6379&gt; get key7<br>“W”<br>127.0.0.1:6379&gt; BITOP AND key8 key6 key7<br>(integer) 1<br>127.0.0.1:6379&gt; get key8<br>“A</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/bitmap按位与.png" alt="bitmap按位与"></p>
<ul>
<li><p>场景-网站用户上线次数统计</p>
<p>  网站用户的上线次数统计（活跃用户）<br>  用户 ID 为 key，天作为 offset，上线置为 1<br>  366 个 0，366 /8 ≈ 50Byte<br>  ID 为 500 的用户，今年的第 1 天上线、第 30 天上线<br>  SETBIT u500 1 1<br>  SETBIT u500 30 1<br>  BITCOUNT u500<br>  按天统计网站活跃用户<br>  天作为 key，用户 ID 为 offset，上线置为 1<br>  求一段时间内活跃用户数<br>  SETBIT 20160601 15 1<br>  SETBIT 20160603 123 1<br>  SETBIT 20160606 123 1<br>  求 6 月 1 日到 6 月 10 日的活跃用户<br>  BITOP OR aaa 20160601 20160602 20160603…. 20160610<br>  BITCOUNT aaa 0 -1</p>
<h3 id="List"><a href="#List" class="headerlink" title="List"></a>List</h3><p>&ensp;&ensp;&ensp;&ensp;&ensp;基于 Linked List 实现，元素是字符串类型，列表头尾增删快，中间增删慢，增删元素是常态，元素可以重复出现，最多包含 2^32-1 元素。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/List_01.png" alt="List数据结构例子"><br>列表的索引，从左至右，从 0 开始，从右至左，从-1 开始<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/List_02.png" alt="List数据结构例子"></p>
</li>
</ul>
<blockquote>
<p>127.0.0.1:6379&gt; lpush list1 a b c<br>(integer) 3<br>127.0.0.1:6379&gt; lpop list1<br>“c”<br>127.0.0.1:6379&gt; rpop list1<br>“a”<br>返回列表中指定范围元素<br>127.0.0.1:6379&gt; lrange list1 0 -1<br>1) “b”<br>获取指定位置的元素<br>127.0.0.1:6379&gt; lindex list1 0 -1<br>“b”<br>设置指定位置元素的值<br>127.0.0.1:6379&gt; lset list1 0 a<br>OK<br>127.0.0.1:6379&gt; lindex list1 0<br>“a”<br>列表长度<br>127.0.0.1:6379&gt; llen list1<br>(integer) 1<br>移除重复元素<br>LREM key count value<br>count &gt; 0 : 从表头开始向表尾搜索，移除与 value 相等的元素，数量为 count<br>count &lt; 0 : 从表尾开始向表头搜索，移除与 value 相等的元素，数量为 count 的绝对值<br>count = 0 : 移除表中所有与 value 相等的值<br>127.0.0.1:6379&gt; lpush list2 a a a b bb bbb c cc cc ccc<br>(integer) 10</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/List_03.png" alt="List数据结构例子"></p>
<blockquote>
<p>127.0.0.1:6379&gt; lrem list2 2 cc<br>(integer) 2</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/List_04.png" alt="List数据结构例子"></p>
<blockquote>
<p>127.0.0.1:6379&gt; lrange list2 0 -1<br>1) “ccc”<br>2) “c”<br>3) “bbb”<br>4) “bb”<br>5) “b”<br>6) “a”<br>7) “a”<br>8) “a”<br>去除指定范围外元素<br>127.0.0.1:6379&gt; ltrim list2 0 5<br>OK<br>127.0.0.1:6379&gt; lrange list2 0 -1<br>1) “ccc”<br>2) “c”<br>3) “bbb”<br>4) “bb”<br>5) “b”<br>6) “a”<br>在列表中某个存在的值前或后插入元素<br>如果 key 和指定位置的值不存在，不进行任何操作<br>127.0.0.1:6379&gt; linsert list2 before c jed<br>(integer) 7<br>127.0.0.1:6379&gt; linsert list2 after c tom<br>(integer) 8<br>127.0.0.1:6379&gt; lrange list2 0 -1<br>1) “ccc”<br>2) “jed”<br>3) “c”<br>4) “tom”<br>5) “bbb”<br>6) “bb”<br>7) “b”<br>8) “a”<br>如果弹出的列表不存在或者为空，就会阻塞<br>超时时间设置为 0，就是永久阻塞，直到有数据可以弹出<br>如果多个客户端阻塞在同一个列表上，使用 First In First Service 原则，先到先服务<br>list3 不存在，发生永久阻塞，复制两台 redis 客户端<br>127.0.0.1:6379&gt; brpop list3 0<br>127.0.0.1:6379&gt; brpop list3 0</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/List_05.png" alt="List数据结构例子"></p>
<h3 id="Hash"><a href="#Hash" class="headerlink" title="Hash"></a>Hash</h3><p>&ensp;&ensp;&ensp;&ensp;由 field 和关联的 value 组成的 map 键值对，field 和 value 是字符串类型，一个 hash 中最多包含 2^32-1 键值对。</p>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Hash_01.png" alt="Hash数据结构例子"></p>
<h4 id="hash常用命令"><a href="#hash常用命令" class="headerlink" title="hash常用命令"></a>hash常用命令</h4><blockquote>
<p>127.0.0.1:6379&gt; hset message id 1<br>(integer) 1<br>127.0.0.1:6379&gt; hmset message name jed sex 1 age 20<br>OK<br>127.0.0.1:6379&gt; hlen message<br>(integer) 4<br>127.0.0.1:6379&gt; hexists message id<br>(integer) 1<br>127.0.0.1:6379&gt; hget message name<br>“jed”<br>127.0.0.1:6379&gt; hmget message id name sex age<br>1) “1”<br>2) “jed”<br>3) “1”<br>4) “20”<br>127.0.0.1:6379&gt; hgetall message<br>1) “id”<br>2) “1”<br>3) “name”<br>4) “jed”<br>5) “sex”<br>6) “1”<br>7) “age”<br>8) “20”<br>127.0.0.1:6379&gt; hkeys message<br>1) “id”<br>2) “name”<br>3) “sex”<br>4) “age”<br>127.0.0.1:6379&gt; hvals message<br>1) “1”<br>2) “jed”<br>3) “1”<br>4) “20”<br>在字段对应的值上进行整数或浮点数的增量计算<br>127.0.0.1:6379&gt; hincrby message id 1<br>(integer) 2<br>127.0.0.1:6379&gt; hincrbyfloat message id -0.5<br>“1.5”<br>删除指定的字段<br>127.0.0.1:6379&gt; hdel message id<br>(integer) 1<br>127.0.0.1:6379&gt; hkeys message<br>1) “name”<br>2) “sex”<br>3) “age”</p>
</blockquote>
<h4 id="用途"><a href="#用途" class="headerlink" title="用途"></a>用途</h4><ul>
<li>节约内存空间：<br>&ensp;&ensp;&ensp;&ensp;每创建一个键，它都会为这个键储存一些附加的管理信息（比如这个键的类型，这个键最后一次被访问的时间等等），所以数据库里面的键越多，redis 数据库服务器在储存附加管理信息方面耗费的内存就越多，花在管理数据库键上的 CPU 也会越多在字段对应的值上进行浮点数的增量计算。</li>
</ul>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Hash_02.png" alt="Hash数据结构例子"></p>
<ul>
<li>不适合 hash 的情况：<br>&ensp;&ensp;&ensp;&ensp;使用二进制位操作命令，因为 Redis 目前支持对字符串键进行 SETBIT、GETBIT、BITOP等操作，如果你想使用这些操作，那么只能使用字符串键，虽然散列也能保存二进制数据使用过期键功能，Redis 的键过期功能目前只能对键进行过期操作，而不能对散列的字段进行过期操作，因此如果你要对键值对数据使用过期功能的话，那么只能把键值对储存在字符串里面。<h4 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h4><h5 id="微博好友关注"><a href="#微博好友关注" class="headerlink" title="微博好友关注"></a>微博好友关注</h5><blockquote>
<p>用户 ID 为 key，field 为好友 ID，Value 为关注时间<br>user:1000 fiend:jed time:20150808<br>user:1000 friend:tom time:20150909<br>用户维度统计<br>统计数包括：关注数、粉丝数、喜欢商品数、发帖数<br>用户为 Key，不同维度为 Field，Value 为统计数<br>比如关注了 5 人<br>HSET user:100000 follow 5 fans 5 collections 10 article 5<br>HINCRBY user:100000 follow 1</p>
</blockquote>
</li>
</ul>
<h3 id="set"><a href="#set" class="headerlink" title="set"></a>set</h3><p>无序的、去重的，元素是字符串类型，最多包含 2^32-1 元素。</p>
<h4 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h4><blockquote>
<p>增加一个或多个元素，如果元素已经存在，则自动忽略<br>127.0.0.1:6379&gt; sadd friends peter tom john<br>(integer) 3<br>127.0.0.1:6379&gt; sadd friends peter bob<br>(integer) 1<br>返回集合包含的所有元素<br>127.0.0.1:6379&gt; smembers friends<br>1) “bob”<br>2) “peter”<br>3) “john”<br>4) “tom”<br>移除一个或者多个元素，元素不存在，自动忽略<br>127.0.0.1:6379&gt; srem friends peter<br>(integer) 1<br>127.0.0.1:6379&gt; smembers friends<br>1) “bob”<br>2) “john”<br>3) “tom”<br>检查给定元素是否存在于集合中<br>127.0.0.1:6379&gt; sismember friends peter<br>(integer) 0<br>集合的无序性：可能返回不同的结果<br>127.0.0.1:6379&gt; sadd s1 a b c d e<br>(integer) 5<br>127.0.0.1:6379&gt; sadd s2 e d b a c<br>(integer) 5<br>127.0.0.1:6379&gt; smembers s1<br>1) “c”<br>2) “b”<br>3) “a”<br>4) “d”<br>5) “e”<br>127.0.0.1:6379&gt; smembers s2<br>1) “b”<br>2) “e”<br>3) “a”<br>4) “d”<br>5) “c”<br>随机返回集合中指定个数的元素<br>如果 count 为正数，且小于集合基数，那么命令返回一个包含 count 个元素的数组，数组中的元素各不相同。如果 count 大于等于集合基数，那么返回整个集合<br>如果 count 为负数，那么命令返回一个数组，数组中的元素可能会重复出现多次，而数组的长度为 count 的绝对值<br>count &lt; 0，长度为 count 绝对值，元素可能重复<br>如果 count 为 0，返回空<br>如果 count 不指定，随机返回一个元素<br>127.0.0.1:6379&gt; srandmember s1 2<br>1) “b”<br>2) “e”<br>返回集合中元素的个数,键的结果会保存信息,集合长度就记录在里面,所以不需要遍历<br>127.0.0.1:6379&gt; scard s1<br>(integer) 5<br>随机从集合中移除并返回这个被移除的元素<br>127.0.0.1:6379&gt; spop s1<br>“a”<br>把元素从源集合移动到目标集合<br>127.0.0.1:6379&gt; smove s1 s3 d<br>(integer) 1<br>127.0.0.1:6379&gt; smembers s3<br>1) “d”</p>
</blockquote>
<h4 id="差集"><a href="#差集" class="headerlink" title="差集"></a>差集</h4><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Set_01.png" alt="Set差集"></p>
<blockquote>
<p>127.0.0.1:6379&gt; sadd n1 1 2 3 4 5 6 7 8 9<br>(integer) 9<br>127.0.0.1:6379&gt; sadd n2 1 2 3 4 5 6 9 9 9<br>(integer) 7<br>127.0.0.1:6379&gt; sdiff n1 n2<br>1) “7”<br>2) “8”<br>127.0.0.1:6379&gt; sdiffstore n1-n2 n1 n2<br>(integer) 2<br>127.0.0.1:6379&gt; smembers n1-n2<br>1) “7”<br>2) “8”</p>
</blockquote>
<h4 id="并集"><a href="#并集" class="headerlink" title="并集"></a>并集</h4><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Set_02.png" alt="Set并集"></p>
<p>SUNION key1 [key2] 返回所有给定集合的并集<br>SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中</p>
<blockquote>
<p>127.0.0.1:6379&gt; SUNIONSTORE n1Un2 n1 n2<br>integer) 9<br>127.0.0.1:6379&gt; smembers n1Un2<br>1) “1”<br>2) “2”<br>3) “3”<br>4) “4”<br>5) “5”<br>6) “6”<br>7) “7”<br>8) “8”<br>9) “9”</p>
</blockquote>
<h5 id="交集"><a href="#交集" class="headerlink" title="交集"></a>交集</h5><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Set_03.png" alt="Set交集"></p>
<blockquote>
<p>127.0.0.1:6379&gt; sinterstore n1In2 n1 n2<br>(integer) 7<br>127.0.0.1:6379&gt; smembers n1In2<br>1) “1”<br>2) “2”<br>3) “3”<br>4) “4”<br>5) “5”<br>6) “6”<br>7) “9”</p>
</blockquote>
<h5 id="场景-1"><a href="#场景-1" class="headerlink" title="场景"></a>场景</h5><ul>
<li>浪微博的共同关注<blockquote>
<p>需求：当用户访问另一个用户的时候，会显示出两个用户共同关注哪些相同的用户<br>设计：将每个用户关注的用户放在集合中，求交集即可<br>实现如下：<br>peter={‘john’,’jack’,’may’}<br>ben={‘john’,’jack’,’tom’}<br>那么 peter 和 ben 的共同关注为：<br>SINTER peter ben 结果为 {‘john’,’jack’}</p>
</blockquote>
<h3 id="SortedSet"><a href="#SortedSet" class="headerlink" title="SortedSet"></a>SortedSet</h3>&ensp;&ensp;&ensp;&ensp;类似 Set 集合，有序的、去重的，元素是字符串类型，每一个元素都关联着一个浮点数分值（Score），并按照分值从小到大的顺序排列集合中的元素。分值可以相同，最多包含 2^32-1 元素。<br>一个保存了水果价格的有序集合：<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/SortedSet_01.png" alt="SortedSet_01"></li>
</ul>
<p>一个保存了员工薪水的有序集合：<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/SortedSet_02.png" alt="SortedSet_02"></p>
<h4 id="常用命令-1"><a href="#常用命令-1" class="headerlink" title="常用命令"></a>常用命令</h4><blockquote>
<p>增加一个或多个元素<br>127.0.0.1:6379&gt; zadd fruits 3.2 banana<br>(integer) 1<br>127.0.0.1:6379&gt; zadd fruits 2.0 watermelon<br>(integer) 1<br>127.0.0.1:6379&gt; zadd fruits 4.0 orange 7.0 pear 6.8 apple<br>(integer) 3<br>127.0.0.1:6379&gt; zrange fruits 0 -1<br>1) “watermelon”<br>2) “banana”<br>3) “orange”<br>4) “apple”<br>5) “pear”<br>移除一个或者多个元素，元素不存在，自动忽略<br>127.0.0.1:6379&gt; zrem fruits watermelon pear<br>(integer) 2<br>127.0.0.1:6379&gt; zrange fruits 0 -1<br>1) “banana”<br>2) “orange”<br>3) “apple”<br>显示分值，计算机并不能精确表达每一个浮点数，都是一种近似表达<br>127.0.0.1:6379&gt; zscore fruits banana<br>“3.2000000000000002”<br>增加或者减少分值<br>127.0.0.1:6379&gt; zincrby fruits 1 banana<br>“4.2000000000000002”<br>127.0.0.1:6379&gt; zincrby fruits -0.8 apple<br>“6”<br>返回元素的排名（索引）<br>127.0.0.1:6379&gt; zrank fruits apple<br>(integer) 2<br>127.0.0.1:6379&gt; zrank fruits orange<br>(integer) 0<br>127.0.0.1:6379&gt; zrank fruits banana<br>(integer) 1</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/SortedSet_03.png" alt="SortedSet_03"></p>
<blockquote>
<p>返回元素的逆序排名<br>127.0.0.1:6379&gt; zrevrank fruits orange<br>(integer) 2<br>返回指定索引区间元素<br>ZRANGE key start stop [WITHSCORES]<br>如果 score 相同，则按照字典序 lexicographical order 排列<br>默认按照 score 从小到大，如果需要 score 从大到小排列，使用 ZREVRANGE<br>127.0.0.1:6379&gt; zrevrange fruits 0 -1 withscores<br>1) “apple”<br>2) “6”<br>3) “banana”<br>4) “4.2000000000000002”<br>5) “orange”<br>6) “4”<br>返回指定分值区间元素<br>ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]<br>返回 score 默认属于[min,max]之间，元素按照 score 升序排列，score 相同字典序<br>LIMIT 中 offset 代表跳过多少个元素，count 是返回几个。类似于 Mysql<br>使用小括号，修改区间为开区间，例如(5、(10，-inf 和+inf 表示负无穷和正无穷<br>127.0.0.1:6379&gt; zrangebyscore fruits 4.0 7.0<br>1) “orange”<br>2) “banana”<br>3) “apple”<br>127.0.0.1:6379&gt; zrangebyscore fruits 4.0 7.0 withscores<br>1) “orange”<br>2) “4”<br>3) “banana”<br>4) “4.2000000000000002”<br>5) “apple”<br>6) “6”<br>127.0.0.1:6379&gt; zrangebyscore fruits -inf +inf<br>1) “orange”<br>2) “banana”<br>3) “apple”<br>127.0.0.1:6379&gt; zrangebyscore fruits (4.0 (7.0<br>1) “banana”<br>2) “apple”<br>移除指定排名范围的元素<br>127.0.0.1:6379&gt; zremrangebyrank fruits 0 0<br>(integer) 1<br>127.0.0.1:6379&gt; zrange fruits 0 -1<br>1) “banana”<br>2) “apple”<br>返回集合中元素个数<br>127.0.0.1:6379&gt; zcard fruits<br>(integer) 2</p>
</blockquote>
<h4 id="并集-1"><a href="#并集-1" class="headerlink" title="并集"></a>并集</h4><p>ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight] [AGGREGATE<br>SUM|MIN|MAX]<br>numkeys 指定 key 的数量，必须<br>WEIGHTS 选项，与前面设定的 key 对应，对应 key 中每一个 score 都要乘以这个权重<br>AGGREGATE 选项，指定并集结果的聚合方式<br>SUM：将所有集合中某一个元素的 score 值之和作为结果集中该成员的 score 值<br>MIN：将所有集合中某一个元素的 score 值中最小值作为结果集中该成员的 score 值<br>MAX：将所有集合中某一个元素的 score 值中最大值作为结果集中该成员的 score 值</p>
<blockquote>
<p>127.0.0.1:6379&gt; zadd score1 70 tom 80 peter 60 jed<br>(integer) 3<br>127.0.0.1:6379&gt; zadd score2 90 peter 60 ben<br>(integer) 2<br>127.0.0.1:6379&gt; zunionstore score-union-1 2 score1 score2<br>(integer) 4<br>127.0.0.1:6379&gt; zrange score-union-1 0 -1<br>1) “ben”<br>2) “jed”<br>3) “tom”<br>4) “peter”<br>127.0.0.1:6379&gt; zunionstore score-union-2 2 score1 score2 aggregate sum<br>(integer) 4<br>127.0.0.1:6379&gt; zrange score-union-2 0 -1 withscores<br>1) “ben”<br>2) “60”<br>3) “jed”<br>4) “60”<br>5) “tom”<br>6) “70”<br>7) “peter”<br>8) “170”<br>127.0.0.1:6379&gt; zunionstore score-union-3 2 score1 score2 weights 1 0.5 aggregate sum<br>(integer) 4<br>127.0.0.1:6379&gt; zrange score-union-3 0 -1 withscores<br>1) “ben”<br>2) “30”<br>3) “jed”<br>4) “60”<br>5) “tom”<br>6) “70”<br>7) “peter”<br>8) “125”</p>
</blockquote>
<h4 id="交集-1"><a href="#交集-1" class="headerlink" title="交集"></a>交集</h4><p>ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]<br>numkeys 指定 key 的数量，必须<br>WEIGHTS 选项，与前面设定的 key 对应，对应 key 中每一个 score 都要乘以这个权重<br>AGGREGATE 选项，指定并集结果的聚合方式<br>SUM：将所有集合中某一个元素的 score 值之和作为结果集中该成员的 score 值<br>MIN：将所有集合中某一个元素的 score 值中最小值作为结果集中该成员的 score 值<br>MAX：将所有集合中某一个元素的 score 值中最大值作为结果集中该成员的 score 值</p>
<h4 id="场景-2"><a href="#场景-2" class="headerlink" title="场景"></a>场景</h4><ul>
<li><p>网易音乐播放量排行榜<br>&ensp;&ensp;&ensp;&ensp;每首歌的歌名作为元素（先不考虑重复），每首歌的播放次数作为分值，ZREVRANGE<br>来获取播放次数最多的歌曲（就是最多播放榜了，云音乐热歌榜，没有竞价，没有权<br>重）。</p>
</li>
<li><p>新浪微博翻页<br>&ensp;&ensp;&ensp;&ensp;新闻网站、博客、论坛、搜索引擎，页面列表条目多，都需要分页，blog 这个 key 中<br>使用时间戳作为 score，ZADD blog 1407000000 ‘今天天气不错’，ZADD blog 1450000000 ‘今<br>天我们学习 Redis’，ZADD blog 1560000000 ‘几个 Redis 使用示例’，ZREVRANGE blog 10 20</p>
</li>
<li><p>京东图书畅销榜</p>
</li>
</ul>
<blockquote>
<p>ZADD bookboard-001 1000 ‘java’ 1500 ‘Redis’ 2000 ‘haoop’<br>ZADD bookboard-002 1020 ‘java’ 1500 ‘Redis’ 2100 ‘haoop’<br>ZADD bookboard-003 1620 ‘java’ 1510 ‘Redis’ 3000 ‘haoop’<br>ZUNIONSTORE bookboard-001:003 3 bookboard-001 bookboard-002 bookboard-003<br>每天的数量累加<br>ZADD bookboard-001 1000 ‘java’ 1500 ‘Redis’ 2000 ‘haoop’<br>ZADD bookboard-002 1020 ‘java’ 1500 ‘Redis’ 2100 ‘haoop’<br>ZADD bookboard-003 1620 ‘java’ 1510 ‘Redis’ 3000 ‘haoop’<br>ZUNIONSTORE bookboard-001:003 3 bookboard-001 bookboard-002 bookboard-003<br>AGGREGATE MAX<br>并集，使用 max，适用于需要计算每日增量的情况<br>注意：参与并集运算的集合较多，会造成 Redis 服务器阻塞，因此最好放在空闲时间<br>或者备用服务器上进行计算</p>
</blockquote>
<h3 id="Redis持久化"><a href="#Redis持久化" class="headerlink" title="Redis持久化"></a>Redis持久化</h3><h4 id="RDB-Redis-DB，默认开启"><a href="#RDB-Redis-DB，默认开启" class="headerlink" title="RDB(Redis DB，默认开启)"></a>RDB(Redis DB，默认开启)</h4><p>&ensp;&ensp;&ensp;&ensp;在默认情况下，Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中<br>&ensp;&ensp;&ensp;&ensp;自动：按照配置文件中的条件满足就执行 BGSAVE，save 60 1000，Redis 如果满足在 60秒内至少有 1000 个键被改动，那么就会自动保存一次<br>&ensp;&ensp;&ensp;&ensp;手动：客户端发起 SAVE、BGSAVE 命令</p>
<ul>
<li>SAVE<br>&ensp;&ensp;&ensp;&ensp;阻塞 Redis 服务，无法响应客户端请求，创建新的 dump.rdb 替代旧文件<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis持久化_01.png" alt="redis持久化之SAVE"></li>
<li>BGSAVE<br>&ensp;&ensp;&ensp;&ensp;非阻塞，Redis 服务正常接收处理客户端请求，Redis 会 fork()一个新的子进程来创建RDB 文件，子进程处理完后会向父进程发送一个信号，通知它处理完毕，父进程用新的dump.rdb 替代旧文件。BGSAVE 是一个异步命令。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis持久化_02.png" alt="redis持久化之BGSAVE"></li>
</ul>
<p>&ensp;&ensp;&ensp;&ensp;假如内存是 512G，目前 redis 占用 40G，当执行 bgsave 时，子进程内存中存储父进程内存的指向，不必开辟真实 800G 的内存占用，父进程不会阻塞，如果在 bgsave 过程中客户端修改了父进程的内存，比如发生了写动作，那么父进程会发生阻塞，fork 实行 copy on write 机制，把修改前的父进程的值 copy 到子进程的内存中，copy 完成后才允许客户端的下一个修改操作。<br>&ensp;&ensp;&ensp;&ensp;BGSAVE 09:00 开始执行，10:00 完成，持久化数据存储的是 09:00 的数据。</p>
<ul>
<li>SAVE和BGSAVE比较</li>
</ul>
<blockquote>
<ol>
<li>SAVE 不用创建新的进程，速度略快；</li>
<li>BGSAVE 需要创建子进程，消耗额外的内存；</li>
<li>SAVE 适合停机维护，服务低谷时段；</li>
<li>BGSAVE 适合线上执行；</li>
</ol>
</blockquote>
<ul>
<li>自动执行<br>本质上就是 BGSAVE<br>默认配置：</li>
</ul>
<blockquote>
<p>save 900 1<br>save 300 10<br>save 60 10000</p>
</blockquote>
<p>只要上面三个条件满足一个，就自动执行备份。<br>创建 RDB 文件之后，时间计数器和次数计数器会清零。所以多个条件的效果不是叠加的</p>
<blockquote>
<p>[root@node01 redis]# vi /etc/redis/6379.conf<br>#默认情况下 redis 不是作为守护进程运行的，设置为 yes 让它在后台运行<br>daemonize yes<br>#端口号<br>port 6379<br>#日志级别<br>loglevel notice<br>#日志位置<br>logfile /var/log/redis_6379.log<br>#一个 redis 实例最多可以包含多少个数据库<br>databases 16<br># Save the DB on disk:<br>save 900 1<br>save 300 10<br>save 60 10000<br># The filename where to dump the DB<br>dbfilename dump.rdb<br># The DB will be written inside this directory, with the filename specified<br># above using the ‘dbfilename’ configuration directive.<br>dir /var/lib/redis/6379<br>#如果启动多个 redis，最好指定每个占用的内存<br># maxmemory &lt;bytes&gt;</p>
</blockquote>
<h5 id="RDB优缺点"><a href="#RDB优缺点" class="headerlink" title="RDB优缺点"></a>RDB优缺点</h5><ul>
<li><p>优点</p>
<blockquote>
<p>完全备份，不同时间的数据集备份可以做到多版本恢复<br>紧凑的单一文件，方便网络传输，适合灾难恢复<br>恢复大数据集速度较 AOF 快</p>
</blockquote>
</li>
<li><p>缺点</p>
</li>
</ul>
<blockquote>
<p>会丢失最近写入、修改的而未能持久化的数据<br>folk 过程非常耗时，会造成毫秒级不能响应客户端请求</p>
</blockquote>
<h5 id="生产环境如何使用RDB"><a href="#生产环境如何使用RDB" class="headerlink" title="生产环境如何使用RDB"></a>生产环境如何使用RDB</h5><p>&ensp;&ensp;&ensp;&ensp;创建一个定时任务 cron job，每小时或者每天将 dump.rdb 复制到指定目录，确保备份文件名称带有日期时间信息，便于管理和还原对应的时间点的快照版本，定时任务删除过期的备份，如果有必要，跨物理主机、跨机架、异地备份。</p>
<h4 id="AOF（Append-only-file）"><a href="#AOF（Append-only-file）" class="headerlink" title="AOF（Append only file）"></a>AOF（Append only file）</h4><p>&ensp;&ensp;&ensp;&ensp;Append only file，采用追加的方式保存，默认文件 appendonly.aof，记录所有的写操作命令，在服务启动的时候使用这些命令就可以还原数据库。调整 AOF 持久化策略，可以在服务出现故障时，不丢失任何数据，也可以丢失一秒的数据。相对于 RDB 损失小得多。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/AOF_01.png" alt="AOF"></p>
<ul>
<li>AOF 写入机制</li>
</ul>
<blockquote>
<p>AOF 方式不能保证绝对不丢失数据，目前常见的操作系统中，执行系统调用 write 函数，将一些内容写入到某个文件里面时，为了提高效率，系统通常不会直接将内容写入硬盘里面，而是先将内容放入一个内存缓冲区（buffer）里面，等到缓冲区被填满，或者用户执行fsync 调用和 fdatasync 调用时才将储存在缓冲区里的内容真正的写入到硬盘里，未写入磁盘之前，数据可能会丢失。</p>
</blockquote>
<ul>
<li>写入磁盘的策略</li>
</ul>
<blockquote>
<p>appendfsync 选项，这个选项的值可以是 always、everysec 或者 no<br>always：服务器每写入一个命令，就调用一次 fdatasync，将缓冲区里面的命令写入到硬盘。这种模式下，服务器出现故障，也不会丢失任何已经成功执行的命令数据<br>everysec（默认）：服务器每一秒重调用一次 fdatasync，将缓冲区里面的命令写入到硬盘。这种模式下，服务器出现故障，最多只丢失一秒钟内的执行的命令数据<br>no：服务器不主动调用 fdatasync，由操作系统决定何时将缓冲区里面的命令写入到硬盘。这种模式下，服务器遭遇意外停机时，丢失命令的数量是不确定的<br>运行速度：always 的速度慢，everysec 和 no 都很快</p>
</blockquote>
<ul>
<li>AOF重写机制<br>AOF 文件过大，合并重复的操作，AOF 会使用尽可能少的命令来记录</li>
</ul>
<blockquote>
<p>重写过程：<br>fork 一个子进程负责重写 AOF 文件<br>子进程会创建一个临时文件写入 AOF 信息<br>父进程会开辟一个内存缓冲区接收新的写命令<br>子进程重写完成后，父进程会获得一个信号，将父进程接收到的新的写操作由子进程写入到临时文件中<br>新文件替代旧文件<br>注：如果写入操作的时候出现故障导致命令写半截，可以使用 redis-check-aof 工具修复</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/AOF_02.png" alt="AOF"></p>
<ul>
<li>手动触发</li>
</ul>
<blockquote>
<p>手动：客户端向服务器发送 BGREWRITEAOF 命令<br>自动：配置文件中的选项，自动执行 BGREWRITEAOF 命令<br>auto-aof-rewrite-min-size <size>，触发 AOF 重写所需的最小体积：只要在 AOF 文件的体积大于等于 size 时，才会考虑是否需要进行 AOF 重写，这个选项用于避免对体积过小的AOF 文件进行重写<br>auto-aof-rewrite-percentage <percent>，指定触发重写所需的 AOF 文件体积百分比：当AOF 文件的体积大于 auto-aof-rewrite-min-size 指定的体积，并且超过上一次重写之后的AOF 文件体积的 percent %时，就会触发 AOF 重写。（如果服务器刚刚启动不久，还没有进行过 AOF 重写，那么使用服务器启动时载入的 AOF 文件的体积来作为基准值）。将这个值<br>设置为 0 表示关闭自动 AOF 重写<br>比如：<br>auto-aof-rewrite-percentage 100<br>auto-aof-rewrite-min-size 64mb<br>当 AOF 文件大于 64MB 时候，可以考虑重写 AOF 文件<br>只有当 AOF 文件的增量大于起始 size 的 100%时（就是文件大小翻了一倍）,启动重写<br>appendonly yes<br>默认关闭，请开启</percent></size></p>
</blockquote>
<ul>
<li>AOF优缺点</li>
</ul>
<blockquote>
<p>优点<br>写入机制，默认 fysnc 每秒执行，性能很好不阻塞服务，最多丢失一秒的数据<br>重写机制，优化 AOF 文件<br>如果误操作了（FLUSHALL 等），只要 AOF 未被重写，停止服务移除 AOF 文件尾部FLUSHALL 命令，重启 Redis，可以将数据集恢复到 FLUSHALL 执行之前的状态<br>缺点<br>相同数据集，AOF 文件体积较 RDB 大了很多<br>恢复数据库速度叫 RDB 慢（文本，命令重演）</p>
</blockquote>
<h3 id="Redis集群"><a href="#Redis集群" class="headerlink" title="Redis集群"></a>Redis集群</h3><h4 id="主从复制-Replication"><a href="#主从复制-Replication" class="headerlink" title="主从复制 Replication"></a>主从复制 Replication</h4><p>&ensp;&ensp;&ensp;&ensp;一个 Redis 服务可以有多个该服务的复制品，这个 Redis 服务称为 Master，其他复制品称为 Slaves，只要网络连接正常，Master 会一直将自己的数据更新同步给 Slaves，保持主从同步，只有 Master 可以执行写命令，Slaves 只能执行读命令。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_01.png" alt="Redis集群"><br>&ensp;&ensp;&ensp;&ensp;从服务器执行客户端发送的读命令，比如 GET、LRANGE、SMEMMBERS、HGET、ZRANGE 等等，客户端可以连接 Slaves 执行读请求，来降低 Master 的读压力。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_02.png" alt="Redis集群"></p>
<ul>
<li>配置主从机制</li>
</ul>
<blockquote>
<p>使用 6380 端口启动 redis 服务作为 master<br>[root@node01 ~]# mkdir 6380<br>[root@node01 ~]# cd 6380<br>[root@node01 6380]# redis-server –port 6380<br>[1315] 25 Jul 12:38:17.551 <em> Increased maximum number of open files to 10032 (it was<br>originally set to 1024).<br>[1315] 25 Jul 12:38:17.561 </em> The server is now ready to accept connections on port 6380<br>用 6381 端口启动 redis 服务作为 slave<br>[root@node01 6381]# redis-server –port 6381 –slaveof 192.168.9.11 6380<br>[1320] 25 Jul 12:44:26.444 <em> Connecting to MASTER 192.168.9.11:6380<br>6380 提示：<br>[1315] 25 Jul 12:44:26.485 </em> Synchronization with slave 192.168.9.11:6381 succeeded<br>说明连接成功<br>启动 6380 和 6381 客户端做测试<br>[root@node01 ~]# redis-cli -p 6380<br>127.0.0.1:6380&gt; set k1 master<br>OK<br>127.0.0.1:6380&gt; get k1<br>“master”<br>[root@node01 ~]# redis-cli -p 6381<br>127.0.0.1:6381&gt; get k1<br>“master”<br>Slave 不能做写动作<br>127.0.0.1:6381&gt; set k2 slave<br>(error) READONLY You can’t write against a read only slave.<br>撤销 6381 的 slave 属性<br>127.0.0.1:6381&gt; slaveof no one<br>OK<br>127.0.0.1:6381&gt; set k2 slave<br>OK<br>127.0.0.1:6381&gt; get k2<br>“slave”<br>127.0.0.1:6381&gt; get k1<br>“master”<br>再让 6381 成为 6380 的 slave<br>127.0.0.1:6381&gt; slaveof 192.168.9.11 6380<br>OK<br>master 和 slave 的数据同步，都存 master 的数据<br>127.0.0.1:6381&gt; keys *<br>1) “k1”</p>
</blockquote>
<p>还可以通过配置方式实现主从辅助：<br>&ensp;&ensp;&ensp;&ensp;启动时，服务器读取配置文件，并自动成为指定服务器的从服务器<br>&ensp;&ensp;&ensp;&ensp;slaveof &lt;masterip&gt; &lt;masterport&gt;<br>&ensp;&ensp;&ensp;&ensp;例如：slaveof 127.0.0.1 6379</p>
<ul>
<li>主从复制问题和哨兵机制的引入<br>&ensp;&ensp;&ensp;&ensp;一个 Master 可以有多个 Slaves，Slave 下线，只是读请求的处理性能下降，Master 下线，写请求无法执行，其中一台 Slave 使用 SLAVEOF no one 命令成为 Master，其它 Slaves执行 SLAVEOF 命令指向这个新的 Master，会从它这里同步数据，以上过程是手动的，能够实现自动，这就需要 Sentinel 哨兵，实现故障转移 Failover 操作。</li>
</ul>
<h4 id="哨兵Sentinel"><a href="#哨兵Sentinel" class="headerlink" title="哨兵Sentinel"></a>哨兵Sentinel</h4><p>&ensp;&ensp;&ensp;&ensp;官方提供的高可用方案，可以用它管理多个 Redis 服务实例，编译后产生 redis-sentinel程序文件，Redis Sentinel 是一个分布式系统，可以在一个架构中运行多个 Sentinel 进程。</p>
<ul>
<li><p>监控 Monitoring<br>&ensp;&ensp;&ensp;&ensp;Sentinel 会不断检查 Master 和 Slaves 是否正常，每一个 Sentinel 可以监控任意多个Master 和该 Master 下的 Slaves。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_03.png" alt="Redis集群"></p>
</li>
<li><p>Sentinel 网络<br>&ensp;&ensp;&ensp;&ensp;监控同一个 Master 的 Sentinel 会自动连接，组成一个分布式的 Sentinel 网络，互相通信并交换彼此关于被监视服务器的信息。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_04.png" alt="Redis集群"></p>
</li>
</ul>
<blockquote>
<p>当一个 sentinel 认为被监视的服务器已经下线时，它会向网络中的其他 Sentinel 进行确认，判断该服务器是否真的已经下线，如果下线的服务器为主服务器，那么 sentinel 网络将对下线主服务器进行自动故障转移，通过将下线主服务器的某个从服务器提升为新的主服务器，并让其从服务器转为复制新的主服务器，以此来让系统重新回到上线的状态，sentinel 网络采用过半机制来判断服务器是否下线，当超过半数的 sentinel 认为服务器已经下线，就进行故障转移，而认为服务器没有下线（可能是由于网络原因无法提交信息给其他 sentinel）的那些少数 sentinel 将会不再与多数的 sentinel 通信，也不会提供服务。当服务器下线后重新上线，它也不会变为 master。</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_05.png" alt="Redis集群"></p>
<ul>
<li><p>当服务器下线后重新上线，它也不会变为 master<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_06.png" alt="Redis集群"></p>
</li>
<li><p>开启哨兵Sentinel</p>
</li>
</ul>
<blockquote>
<p>进入 redis 编译包下的 src 目录<br>[root@node01 src]# cd /opt/sxt/redis-2.8.18/src<br>把 edis-sentinel 文件 copy 到 redis 安装路径的 bin 目录下<br>[root@node01 src]# cp redis-sentinel /opt/sxt/redis/bin/<br>创建 sentinel 的目录<br>[root@node01 src]# cd ~<br>[root@node01 ~]# mkdir 26379<br>创建 sentinel 配置文件并编辑<br>[root@node01 ~]# cd 26379<br>[root@node01 26379]# vi sentinel1.conf<br>port 26379<br>#这里只设置一个 sentinel<br>Sentinel monitor s1 192.168.9.11 6379 1<br>启动 Sentinel<br>[root@node01 26379]# redis-sentinel sentinel1.conf<br>设置 6380 和 6381 位 6379 的 slave 并启动<br>[root@node01 ~]# cd 6380<br>[root@node01 6380]# redis-server –port 6380 –slaveof 192.168.9.11 6379<br>[root@node01 ~]# cd 6381<br>[root@node01 6381]# redis-server –port 6381 –slaveof 192.168.9.11 6379<br>Sentinel 显示添加信息<br>[1375] 25 Jul 13:44:37.238 <em> +slave slave 192.168.9.11:6380 192.168.9.11 6380 @ s1<br>192.168.9.11 6379<br>[1375] 25 Jul 13:45:27.487 </em> +slave slave 192.168.9.11:6381 192.168.9.11 6381 @ s1<br>192.168.9.11 6379<br>把 6379 挂掉<br>[root@node01 ~]# service redis_6379 stop<br>Stopping …<br>Redis stopped<br>Sentinel 把 6381 提为主机<br>[1375] 25 Jul 13:50:04.914 # +switch-master s1 192.168.9.11 6379 192.168.9.11 6381<br>[1375] 25 Jul 13:50:04.914 <em> +slave slave 192.168.9.11:6380 192.168.9.11 6380 @ s1<br>192.168.9.11 6381<br>[1375] 25 Jul 13:50:04.915 </em> +slave slave 192.168.9.11:6379 192.168.9.11 6379 @ s1<br>192.168.9.11 6381</p>
</blockquote>
<ul>
<li>总结:<br>&ensp;&ensp;&ensp;&ensp;主从复制，解决了读请求的分担，从节点下线，会使得读请求能力有所下降;<br>&ensp;&ensp;&ensp;&ensp;Master只有一个，写请求还有单点故障问题;<br>&ensp;&ensp;&ensp;&ensp;Sentinel 会在 Master 下线后自动执行 Failover 操作，提升一台 Slave 为 Master，并让其他 Slaves 重新成为新 Master 的 Slaves;<br>&ensp;&ensp;&ensp;&ensp;主从复制+哨兵 Sentinel 只解决了读性能和高可用问题，但是没有解决写性能问题。</li>
</ul>
<h4 id="Redis-Twemproxy"><a href="#Redis-Twemproxy" class="headerlink" title="Redis Twemproxy"></a>Redis Twemproxy</h4><ul>
<li>问题</li>
</ul>
<blockquote>
<p>主从对写压力没有分担<br>解决思路就是，使用多个节点分担，将写请求分散到不同节点处理<br>分片Sharding：多节点分担的思路就是关系型数据库处理大表的水平切分思路</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_07.png" alt="Redis集群"></p>
<blockquote>
<p>Twemproxy 是 Twitter 开发的代理服务器，他兼容 Redis 和 Memcached，允许用户将多个 redis 服务器添加到一个服务器池（pool）里面，并通过用户选择的散列函数和分布函数，将来自客户端的命令请求分发给服务器池中的各个服务器。<br>通过使用 twemproxy 我们可以将数据库分片到多台 redis 服务器上面，并使用这些服务器来分担系统压力以及数据库容量：在服务器硬件条件相同的情况下，对于一个包含 N 台 redis 服务器的池来说，池中每台平均 1/N 的客户端命令请求。<br>向池里添加更多服务器可以线性的扩展系统处理命令请求的能力，以及系统能够保存的数据量</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_08.png" alt="Redis集群"></p>
<ul>
<li>Twemproxy安装配置与验证略（因为Redis3.X给出了更加完美的解决方案）</li>
</ul>
<blockquote>
<p>总结：<br>前端使用 Twemproxy 做代理，后端的 Redis 数据能基本上根据 key 来进行比较均衡的分布，后端一台 Redis 挂掉后，Twemproxy 能够自动摘除。恢复后，Twemproxy 能够自动识别、恢复并重新加入到 Redis 组中重新使用。Redis 挂掉后，后端数据是否丢失依据 Redis 本身的持久化策略配置，与 Twemproxy 基本无关。如果要新增加一台 Redis，Twemproxy 需要重启才能生效；并且数据不会自动重新 Reblance，需要人工单独写脚本来实现，如原来已经有 2 个节点 Redis，后续有增加 2 个 Redis，则数据分布计算与原来的Redis 分布无关，现有数据如果需要分布均匀的话，需要人工单独处理。如果 Twemproxy 的后端节点数量发生变化，Twemproxy 相同算法的前提下，原来的数据必须重新处理分布，否则会存在找不到 key 值的情况，不管 Twemproxy 后端有几台 Redis，前端的单个Twemproxy 的性能最大也只能和单台 Redis 性能差不多，如同时部署多台 Twemproxy 配置一样，客户端分别连接多台 Twemproxy 可以在一定条件下提高性能。Twemproxy 作为一个第三方的工具，存在一些问题，这里不再详细介绍，具体的解决写性能的问题，redis3.0 给出了较好的解决方案。</p>
</blockquote>
<h3 id="Redis集群3-X"><a href="#Redis集群3-X" class="headerlink" title="Redis集群3.X"></a>Redis集群3.X</h3><blockquote>
<p>由多个 Redis 服务器组成的分布式网络服务集群;<br>每一个 Redis 服务器称为节点Node，节点之间会互相通信。两两相连;<br>Redis 集群无中心节点。</p>
</blockquote>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_09.png" alt="Redis集群"></p>
<ul>
<li>Redis 集群节点复制<br>&ensp;&ensp;&ensp;&ensp;Redis集群的每个节点都有两种角色可选：主节点master node、从节点slave node。其中主节点用于存储数据，而从节点则是某个主节点的复制品。<br>&ensp;&ensp;&ensp;&ensp;当用户需要处理更多读请求的时候，添加从节点可以扩展系统的读性能，因为Redis集群重用了单机Redis复制特性的代码，所以集群的复制行为和我们之前介绍的单机复制特性的行为是完全一样的。</li>
</ul>
<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_10.png" alt="Redis集群"></p>
<ul>
<li><p>Redis 集群故障转移<br>&ensp;&ensp;&ensp;&ensp;Redis集群的主节点内置了类似Redis Sentinel的节点故障检测和自动故障转移功能，当集群中的某个主节点下线时，集群中的其他在线主节点会注意到这一点，并对已下线的主节点进行故障转移。<br>&ensp;&ensp;&ensp;&ensp;集群进行故障转移的方法和Redis Sentinel进行故障转移的方法基本一样，不同的是，在集群里面，故障转移是由集群中其他在线的主节点负责进行的，所以集群不必另外使用Redis Sentinel。<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_11.png" alt="Redis集群"></p>
</li>
<li><p>Redis 集群分片<br>&ensp;&ensp;&ensp;&ensp;集群将整个数据库分为16384个槽位slot，所有key都数据这些slot中的一个，key的槽位计算公式为slot_number=crc16(key)%16384，其中crc16为16位的循环冗余校验和函数<br>&ensp;&ensp;&ensp;&ensp;集群中的每个主节点都可以处理0个至16383个槽，当16384个槽都有某个节点在负责处理时，集群进入上线状态，并开始处理客户端发送的数据命令请求</p>
</li>
</ul>
<blockquote>
<ul>
<li>举例<br>三个主节点7000、7001、7002平均分片16384个slot槽位<br>节点7000指派的槽位为0到5460<br>节点7001指派的槽位为5461到10922<br>节点7002指派的槽位为10923到16383</li>
</ul>
</blockquote>
<ul>
<li>Redis集群Redirect转向<br>&ensp;&ensp;&ensp;&ensp;由于Redis集群无中心节点，请求会发给任意主节点<br>&ensp;&ensp;&ensp;&ensp;主节点只会处理自己负责槽位的命令请求，其它槽位的命令请求，该主节点会返回客户端一个转向错误<br>&ensp;&ensp;&ensp;&ensp;客户端根据错误中包含的地址和端口重新向正确的负责的主节点发起命令请求<br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;<img src="/2018/09/15/Redis命令学习与集群搭建，包含Redis3-X的集群搭建/Redis集群_12.png" alt="Redis集群"></li>
</ul>
<h3 id="搭建-redis3-0-集群"><a href="#搭建-redis3-0-集群" class="headerlink" title="搭建 redis3.0 集群"></a>搭建 redis3.0 集群</h3><p>物理节点 1 个 node01<br>指定 3 个主节点端口 7000 7001 7002<br>对应的 3 个从节点端口 7003 7004 7005</p>
<ul>
<li>安装 redis3.0</li>
</ul>
<blockquote>
<p>[root@node02 sxt]# tar xf redis-3.0.4.tar.gz<br>[root@node02 sxt]# cd redis-3.0.4/<br>[root@node02 redis-3.0.4]# yum install gcc tcl –y<br>[root@node02 redis-3.0.4]# make PREFIX=/opt/sxt/redis.3.0.4-install &amp;&amp; make install<br>[root@node02 redis-3.0.4]# vi + /etc/profile<br>export REDIS_HOME=/opt/sxt/redis.3.0.4-install<br>export PATH=$PATH:$REDIS_HOME/bin<br>[root@node02 redis-3.0.4]# . /etc/profile</p>
</blockquote>
<ul>
<li>安装 ruby</li>
</ul>
<blockquote>
<p>[root@node02 sxt]# yum install ruby rubygems -y</p>
</blockquote>
<ul>
<li>安装 redis 模块（离线安装）</li>
</ul>
<blockquote>
<p>[root@node02 sxt]# gem install –local /usr/local/software/redis-3.3.0.gem</p>
</blockquote>
<ul>
<li>创建 6 个目录并在其中写入对应的配置文件</li>
</ul>
<blockquote>
<p>[root@node02 ~]# mkdir 700{0,1,2,3,4,5}<br>[root@node02 ~]# cd 7000<br>[root@node02 7000]# vi redis.conf<br>cluster-enabled yes<br>port 7000<br>[root@node02 7000]# cd ../7001<br>[root@node02 7001]# vi redis.conf<br>cluster-enabled yes<br>port 7001<br>[root@node02 7001]# cd ../7002<br>[root@node02 7002]# vi redis.conf<br>cluster-enabled yes<br>port 7002<br>[root@node02 7002]# cd ../7003<br>[root@node02 7003]# vi redis.conf<br>cluster-enabled yes<br>port 7003<br>[root@node02 7003]# cd ../7004<br>[root@node02 7004]# vi redis.conf<br>cluster-enabled yes<br>port 7004<br>[root@node02 7004]# cd ../7005<br>[root@node02 7005]# vi redis.conf<br>cluster-enabled yes<br>port 7005</p>
</blockquote>
<ul>
<li>进入对应的子目录启动服务</li>
</ul>
<blockquote>
<p>[root@node02 ~]# cd 7000<br>[root@node02 7000]# redis-server redis.conf<br>[root@node02 ~]# cd 7001<br>[root@node02 7001]# redis-server redis.conf<br>[root@node02 ~]# cd 7002<br>[root@node02 7002]# redis-server redis.conf<br>[root@node02 ~]# cd 7003<br>[root@node02 7003]# redis-server redis.conf<br>[root@node02 ~]# cd 7004<br>[root@node02 7004]# redis-server redis.conf<br>[root@node02 ~]# cd 7005<br>[root@node02 7005]# redis-server redis.conf</p>
</blockquote>
<ul>
<li>在 redis 的编译包的 src 下进行初始化（分槽）</li>
</ul>
<blockquote>
<p>[root@node02 ~]# cd /usr/local/software/redis-3.0.4/src<br>#1 的意义是，6 台机器，每台 1 个 slave，就是 3 台 master，3 台 slave，如果设置为2，就是 2 台 master，每台有 2 个 slave<br>[root@node02 src]# ./redis-trib.rb create –replicas 1<br>127.0.0.1:7000<br>127.0.0.1:7001<br>127.0.0.1:7002<br>127.0.0.1:7003<br>127.0.0.1:7004<br>127.0.0.1:7005<br>>&gt;&gt; Creating cluster<br>Connecting to node 127.0.0.1:7000: OK<br>Connecting to node 127.0.0.1:7001: OK<br>Connecting to node 127.0.0.1:7002: OK<br>Connecting to node 127.0.0.1:7003: OK<br>Connecting to node 127.0.0.1:7004: OK<br>Connecting to node 127.0.0.1:7005: OK<br>>&gt;&gt; Performing hash slots allocation on 6 nodes…<br>Using 3 masters:<br>127.0.0.1:7000<br>127.0.0.1:7001<br>127.0.0.1:7002<br>Adding replica 127.0.0.1:7003 to 127.0.0.1:7000<br>Adding replica 127.0.0.1:7004 to 127.0.0.1:7001<br>Adding replica 127.0.0.1:7005 to 127.0.0.1:7002<br>M: aaaff266e632d3418aeef10854ebb0a7aa32ffe1 127.0.0.1:7000<br> slots:0-5460 (5461 slots) master<br>M: fb284eba4d88746bce6b49eb41a461b1b5b92ed1 127.0.0.1:7001<br> slots:5461-10922 (5462 slots) master<br>M: e6b801b203c699ebbbb921f26bf97a2e8ef2812a 127.0.0.1:7002<br> slots:10923-16383 (5461 slots) master<br>S: 01164141f726f80a4d7cd66c528db6f1514d26e0 127.0.0.1:7003<br> replicates aaaff266e632d3418aeef10854ebb0a7aa32ffe1<br>S: 84acbf4cc75293150045f51f3e474f0ecd2fc622 127.0.0.1:7004<br> replicates fb284eba4d88746bce6b49eb41a461b1b5b92ed1<br>S: db575a5f97223c9a2ca4a37171584a47f5806577 127.0.0.1:7005<br> replicates e6b801b203c699ebbbb921f26bf97a2e8ef2812a<br>Can I set the above configuration? (type ‘yes’ to accept): yes<br>>&gt;&gt; Nodes configuration updated<br>>&gt;&gt; Assign a different config epoch to each node<br>>&gt;&gt; Sending CLUSTER MEET messages to join the cluster<br>Waiting for the cluster to join….<br>>&gt;&gt; Performing Cluster Check (using node 127.0.0.1:7000)<br>M: aaaff266e632d3418aeef10854ebb0a7aa32ffe1 127.0.0.1:7000<br> slots:0-5460 (5461 slots) master<br>M: fb284eba4d88746bce6b49eb41a461b1b5b92ed1 127.0.0.1:7001<br> slots:5461-10922 (5462 slots) master<br>M: e6b801b203c699ebbbb921f26bf97a2e8ef2812a 127.0.0.1:7002<br> slots:10923-16383 (5461 slots) master<br>M: 01164141f726f80a4d7cd66c528db6f1514d26e0 127.0.0.1:7003<br> slots: (0 slots) master<br> replicates aaaff266e632d3418aeef10854ebb0a7aa32ffe1<br>M: 84acbf4cc75293150045f51f3e474f0ecd2fc622 127.0.0.1:7004<br> slots: (0 slots) master<br> replicates fb284eba4d88746bce6b49eb41a461b1b5b92ed1<br>M: db575a5f97223c9a2ca4a37171584a47f5806577 127.0.0.1:7005<br> slots: (0 slots) master<br> replicates e6b801b203c699ebbbb921f26bf97a2e8ef2812a<br>[OK] All nodes agree about slots configuration.<br>>&gt;&gt; Check for open slots…<br>>&gt;&gt; Check slots coverage…<br>[OK] All 16384 slots covered</p>
</blockquote>
<ul>
<li>客户端连接集群做测试</li>
</ul>
<blockquote>
<p>[root@node02 ~]# redis-cli -p 7000 –c<br>127.0.0.1:7000&gt; set k1 hello<br>-&gt; Redirected to slot [12706] located at 127.0.0.1:7002<br>OK<br>127.0.0.1:7002&gt; get k1<br>“hello”<br>停止 7001 的服务后，其 slave7004 变为了 master<br>11137:S 25 Jul 07:53:31.097 # Cluster state changed: fail<br>11137:S 25 Jul 07:53:31.183 # Start of election delayed for 716 milliseconds (rank #0, offset<br>659).<br>11137:S 25 Jul 07:53:31.986 # Starting a failover election for epoch 7.<br>11137:S 25 Jul 07:53:31.989 # Failover election won: I’m the new master.<br>11137:S 25 Jul 07:53:31.989 # configEpoch set to 7 after successful failover<br>11137:M 25 Jul 07:53:31.989 # Connection with master lost.<br>11137:M 25 Jul 07:53:31.989 <em> Caching the disconnected master state.<br>11137:M 25 Jul 07:53:31.989 </em> Discarding previously cached master state.<br>11137:M 25 Jul 07:53:31.989 # Cluster state changed: ok</p>
</blockquote>
<ul>
<li>Redis集群总结<br>&ensp;&ensp;&ensp;&ensp;Redis集群是一个由多个节点组成的分布式服务集群，它具有复制、高可用和分片特性<br>&ensp;&ensp;&ensp;&ensp;Redis的集群没有中心节点，并且带有复制和故障转移特性，这可用避免单个节点成为性能瓶颈，或者因为某个节点下线而导致整个集群下线<br>&ensp;&ensp;&ensp;&ensp;集群中的主节点负责处理槽（储存数据），而从节点则是主节点的复制品<br>&ensp;&ensp;&ensp;&ensp;Redis集群将整个数据库分为16384个槽，数据库中的每个键都属于16384个槽中的其中一个<br>&ensp;&ensp;&ensp;&ensp;集群中的每个主节点都可以负责0个至16384个槽，当16384个槽都有节点在负责时，集群进入上线状态，可以执行客户端发送的数据命令<br>&ensp;&ensp;&ensp;&ensp;主节点只会执行和自己负责的槽有关的命令，当节点接收到不属于自己处理的槽的命令时，它将会处理指定槽的节点的地址返回给客户端，而客户端会向正确的节点重新发送<br>&ensp;&ensp;&ensp;&ensp;如果需要完整地分片、复制和高可用特性，并且要避免使用代理带来的性能瓶颈和资源消耗，那么可以选择使用Redis集群；如果只需要一部分特性（比如只需要分片，但不需要复制和高可用等），那么单独选用twemproxy、Redis的复制和Redis Sentinel中的一个或多个</li>
</ul>
<table>
<thead>
<tr>
<th>—</th>
<th>twemproxy</th>
<th>集群</th>
</tr>
</thead>
<tbody>
<tr>
<td>运作模式</td>
<td>代理模式，代理本身可能成为性能瓶颈，随着负载的增加需要添加更多twemproxy来分担请求负载，但每个twemproxy本身也会消耗一定的资源</td>
<td>分布式，没有中心节点，但是因为每个节点都需要互相进行数据通信，所以在节点数量多时，集群用于进行通信所耗费的网络资源会比较多</td>
</tr>
<tr>
<td>分片</td>
<td>基本上是按照池中的服务器数量N来分片，每个服务器平均占整个数据库的1/N</td>
<td>按照槽来进行分片，通过每个节点指派不同数量的槽，可以控制不同节点负责数据量和请求数量</td>
</tr>
<tr>
<td>复制和高可用</td>
<td>需要配合Redis的复制特性以及Redis Sentinel才能实现复制和高可用</td>
<td>集群的节点内置了复制和高可用特性</td>
</tr>
</tbody>
</table>

      
    </div>
    
    <div class="article-info article-info-index">
      
      

      
    <div class="article-tag tagcloud">
        <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Redis/">Redis</a></li></ul>
    </div>

      
      <div class="clearfix"></div>
    </div>
    
  </div>
  
</article>









  
  
</div>
      <footer id="footer">
    <div class="outer">
        <div id="footer-info">
            <div class="footer-left">
                <i class="fa fa-copyright"></i> 
                2016-2018 Shang Jianli
            </div>
            <div class="footer-right">
                <a href="http://hexo.io/" target="_blank" title="快速、简洁且高效的博客框架">Hexo</a>  Theme <a href="https://github.com/MOxFIVE/hexo-theme-yelee" target="_blank" title="简而不减 Hexo 双栏博客主题  v3.5">Yelee</a> by MOxFIVE <i class="fa fa-heart animated infinite pulse"></i>
            </div>
        </div>
        
            <div class="visit">
                
                    <span id="busuanzi_container_site_pv" style='display:none'>
                        <span id="site-visit" title="本站到访数"><i class="fa fa-user" aria-hidden="true"></i><span id="busuanzi_value_site_uv"></span>
                        </span>
                    </span>
                
                
                    <span>| </span>
                
                
                    <span id="busuanzi_container_page_pv" style='display:none'>
                        <span id="page-visit"  title="本页阅读量"><i class="fa fa-eye animated infinite pulse" aria-hidden="true"></i><span id="busuanzi_value_page_pv"></span>
                        </span>
                    </span>
                
            </div>
        
    </div>
</footer>
    </div>
    
<script data-main="/js/main.js" src="//cdn.bootcss.com/require.js/2.2.0/require.min.js"></script>

    <script>
        $(document).ready(function() {
            var iPad = window.navigator.userAgent.indexOf('iPad');
            if (iPad > -1 || $(".left-col").css("display") === "none") {
                var bgColorList = ["#9db3f4", "#414141", "#e5a859", "#f5dfc6", "#c084a0", "#847e72", "#cd8390", "#996731"];
                var bgColor = Math.ceil(Math.random() * (bgColorList.length - 1));
                $("body").css({"background-color": bgColorList[bgColor], "background-size": "cover"});
            }
            else {
                var backgroundnum = 5;
                var backgroundimg = "url(/background/bg-x.jpg)".replace(/x/gi, Math.ceil(Math.random() * backgroundnum));
                $("body").css({"background": backgroundimg, "background-attachment": "fixed", "background-size": "cover"});
            }
        })
    </script>





<div class="scroll" id="scroll">
    <a href="#" title="返回顶部"><i class="fa fa-arrow-up"></i></a>
    <a href="#comments" onclick="load$hide();" title="查看评论"><i class="fa fa-comments-o"></i></a>
    <a href="#footer" title="转到底部"><i class="fa fa-arrow-down"></i></a>
</div>
<script>
    // Open in New Window
    
        var oOpenInNew = {
            
            
            
             tags: ".article-tag a", 
            
            
             archives: ".archive-article-title", 
             miniArchives: "a.post-list-link", 
            
             friends: "#js-friends a", 
             socail: ".social a" 
        }
        for (var x in oOpenInNew) {
            $(oOpenInNew[x]).attr("target", "_blank");
        }
    
</script>

<script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js">
</script>
  </div>
</body>
</html>